Represent Objective-C block pointers in methods as Kotlin functions
Also do some refactoring.
This commit is contained in:
committed by
SvyatoslavScherbina
parent
caf6719fc4
commit
e8f97b0436
+16
-1
@@ -530,7 +530,7 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
|
||||
|
||||
CXType_ObjCSel -> PointerType(VoidType)
|
||||
|
||||
CXType_BlockPointer -> objCType { ObjCIdType(getNullability(type, typeAttributes), getProtocols(type)) }
|
||||
CXType_BlockPointer -> objCType { convertBlockPointerType(type, typeAttributes) }
|
||||
|
||||
else -> UnsupportedType
|
||||
}
|
||||
@@ -572,6 +572,21 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertBlockPointerType(type: CValue<CXType>, typeAttributes: CValue<CXTypeAttributes>?): ObjCPointer {
|
||||
val kind = type.kind
|
||||
assert(kind == CXType_BlockPointer)
|
||||
|
||||
val pointee = clang_getPointeeType(type)
|
||||
val nullability = getNullability(type, typeAttributes)
|
||||
|
||||
// TODO: also use nullability attributes of parameters and return value.
|
||||
|
||||
val functionType = convertFunctionType(pointee) as? FunctionType
|
||||
?: return ObjCIdType(nullability, protocols = emptyList())
|
||||
|
||||
return ObjCBlockPointer(nullability, functionType.parameterTypes, functionType.returnType)
|
||||
}
|
||||
|
||||
private val TARGET_ATTRIBUTE = "__target__"
|
||||
|
||||
internal fun tokenizeExtent(cursor: CValue<CXCursor>): List<String> {
|
||||
|
||||
+4
@@ -258,5 +258,9 @@ data class ObjCIdType(
|
||||
) : ObjCQualifiedPointer()
|
||||
|
||||
data class ObjCInstanceType(override val nullability: Nullability) : ObjCPointer()
|
||||
data class ObjCBlockPointer(
|
||||
override val nullability: Nullability,
|
||||
val parameterTypes: List<Type>, val returnType: Type
|
||||
) : ObjCPointer()
|
||||
|
||||
object UnsupportedType : Type
|
||||
@@ -86,18 +86,32 @@ inline val ObjCObject?.rawPtr: NativePtr get() = if (this != null) {
|
||||
nativeNullPtr
|
||||
}
|
||||
|
||||
@SymbolName("Kotlin_Interop_createKotlinObjectHolder")
|
||||
external fun createKotlinObjectHolder(any: Any?): NativePtr
|
||||
|
||||
inline fun <reified T : Any> unwrapKotlinObjectHolder(holder: ObjCObject?): T {
|
||||
return unwrapKotlinObjectHolderImpl(holder!!.rawPtr) as T
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
@SymbolName("Kotlin_Interop_unwrapKotlinObjectHolder")
|
||||
external internal fun unwrapKotlinObjectHolderImpl(ptr: NativePtr): Any
|
||||
|
||||
class ObjCObjectVar<T : ObjCObject?>(rawPtr: NativePtr) : CVariable(rawPtr) {
|
||||
companion object : CVariable.Type(pointerSize.toLong(), pointerSize)
|
||||
}
|
||||
|
||||
class ObjCStringVarOf<T : String?>(rawPtr: NativePtr) : CVariable(rawPtr) {
|
||||
class ObjCNotImplementedVar<T : Any?>(rawPtr: NativePtr) : CVariable(rawPtr) {
|
||||
companion object : CVariable.Type(pointerSize.toLong(), pointerSize)
|
||||
}
|
||||
|
||||
var <T : String?> ObjCStringVarOf<T>.value: T
|
||||
var <T : Any?> ObjCNotImplementedVar<T>.value: T
|
||||
get() = TODO()
|
||||
set(value) = TODO()
|
||||
|
||||
typealias ObjCStringVarOf<T> = ObjCNotImplementedVar<T>
|
||||
typealias ObjCBlockVar<T> = ObjCNotImplementedVar<T>
|
||||
|
||||
@konan.internal.Intrinsic external fun getReceiverOrSuper(receiver: NativePtr, superClass: NativePtr): COpaquePointer?
|
||||
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
|
||||
+10
-6
@@ -16,7 +16,11 @@
|
||||
|
||||
package org.jetbrains.kotlin.native.interop.gen
|
||||
|
||||
class NativeCodeBuilder {
|
||||
interface NativeScope {
|
||||
val mappingBridgeGenerator: MappingBridgeGenerator
|
||||
}
|
||||
|
||||
class NativeCodeBuilder(val scope: NativeScope) {
|
||||
val lines = mutableListOf<String>()
|
||||
|
||||
fun out(line: String): Unit {
|
||||
@@ -24,13 +28,13 @@ class NativeCodeBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
inline fun buildNativeCodeLines(block: NativeCodeBuilder.() -> Unit): List<String> {
|
||||
val builder = NativeCodeBuilder()
|
||||
inline fun buildNativeCodeLines(scope: NativeScope, block: NativeCodeBuilder.() -> Unit): List<String> {
|
||||
val builder = NativeCodeBuilder(scope)
|
||||
builder.block()
|
||||
return builder.lines
|
||||
}
|
||||
|
||||
class KotlinCodeBuilder {
|
||||
class KotlinCodeBuilder(val scope: KotlinScope) {
|
||||
private val lines = mutableListOf<String>()
|
||||
|
||||
private val freeStack = mutableListOf<String>()
|
||||
@@ -69,8 +73,8 @@ class KotlinCodeBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
inline fun buildKotlinCodeLines(block: KotlinCodeBuilder.() -> Unit): List<String> {
|
||||
val builder = KotlinCodeBuilder()
|
||||
inline fun buildKotlinCodeLines(scope: KotlinScope, block: KotlinCodeBuilder.() -> Unit): List<String> {
|
||||
val builder = KotlinCodeBuilder(scope)
|
||||
builder.block()
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ class GlobalVariableStub(global: GlobalDecl, stubGenerator: StubGenerator) : Kot
|
||||
|
||||
if (unwrappedType is ArrayType) {
|
||||
kotlinType = (mirror as TypeMirror.ByValue).valueType
|
||||
getter = mirror.info.argFromBridged(getAddressExpression, kotlinScope) + "!!"
|
||||
getter = mirror.info.argFromBridged(getAddressExpression, kotlinScope, nativeBacked = this) + "!!"
|
||||
setter = null
|
||||
} else {
|
||||
val pointedTypeName = mirror.pointedType.render(kotlinScope)
|
||||
|
||||
+31
-11
@@ -34,6 +34,8 @@ interface KotlinScope {
|
||||
* or `null` if the property with given name can't be declared.
|
||||
*/
|
||||
fun declareProperty(name: String): String?
|
||||
|
||||
val mappingBridgeGenerator: MappingBridgeGenerator
|
||||
}
|
||||
|
||||
data class Classifier(
|
||||
@@ -92,14 +94,23 @@ object StarProjection : KotlinTypeArgument {
|
||||
override fun render(scope: KotlinScope) = "*"
|
||||
}
|
||||
|
||||
interface KotlinType : KotlinTypeArgument
|
||||
interface KotlinType : KotlinTypeArgument {
|
||||
val classifier: Classifier
|
||||
fun makeNullableAsSpecified(nullable: Boolean): KotlinType
|
||||
}
|
||||
|
||||
data class KotlinClassifierType(
|
||||
val classifier: Classifier,
|
||||
override val classifier: Classifier,
|
||||
val arguments: List<KotlinTypeArgument>,
|
||||
val nullable: Boolean
|
||||
) : KotlinType {
|
||||
|
||||
override fun makeNullableAsSpecified(nullable: Boolean) = if (this.nullable == nullable) {
|
||||
this
|
||||
} else {
|
||||
this.copy(nullable = nullable)
|
||||
}
|
||||
|
||||
override fun render(scope: KotlinScope): String = buildString {
|
||||
append(scope.reference(classifier))
|
||||
if (arguments.isNotEmpty()) {
|
||||
@@ -113,24 +124,33 @@ data class KotlinClassifierType(
|
||||
}
|
||||
}
|
||||
|
||||
fun KotlinClassifierType.makeNullableAsSpecified(nullable: Boolean) = if (this.nullable == nullable) {
|
||||
this
|
||||
} else {
|
||||
this.copy(nullable = nullable)
|
||||
}
|
||||
|
||||
fun KotlinClassifierType.makeNullable() = this.makeNullableAsSpecified(true)
|
||||
fun KotlinType.makeNullable() = this.makeNullableAsSpecified(true)
|
||||
|
||||
data class KotlinFunctionType(
|
||||
val parameterTypes: List<KotlinType>,
|
||||
val returnType: KotlinType
|
||||
val returnType: KotlinType,
|
||||
val nullable: Boolean = false
|
||||
) : KotlinType {
|
||||
|
||||
override fun makeNullableAsSpecified(nullable: Boolean) = if (this.nullable == nullable) {
|
||||
this
|
||||
} else {
|
||||
this.copy(nullable = nullable)
|
||||
}
|
||||
|
||||
override val classifier by lazy {
|
||||
Classifier.topLevel("kotlin", "Function${parameterTypes.size}")
|
||||
}
|
||||
|
||||
override fun render(scope: KotlinScope) = buildString {
|
||||
if (nullable) append("(")
|
||||
|
||||
append('(')
|
||||
parameterTypes.joinTo(this) { it.render(scope) }
|
||||
append(") -> ")
|
||||
append(returnType.render(scope))
|
||||
|
||||
if (nullable) append(")?")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +214,7 @@ object KotlinTypes {
|
||||
}
|
||||
}
|
||||
|
||||
class KotlinFile(
|
||||
abstract class KotlinFile(
|
||||
val pkg: String,
|
||||
namesToBeDeclared: List<String>
|
||||
) : KotlinScope {
|
||||
|
||||
+11
-8
@@ -26,8 +26,7 @@ import org.jetbrains.kotlin.native.interop.indexer.VoidType
|
||||
*/
|
||||
class MappingBridgeGeneratorImpl(
|
||||
val declarationMapper: DeclarationMapper,
|
||||
val simpleBridgeGenerator: SimpleBridgeGenerator,
|
||||
val kotlinScope: KotlinScope
|
||||
val simpleBridgeGenerator: SimpleBridgeGenerator
|
||||
) : MappingBridgeGenerator {
|
||||
|
||||
override fun kotlinToNative(
|
||||
@@ -57,7 +56,7 @@ class MappingBridgeGeneratorImpl(
|
||||
is RecordType -> {
|
||||
val mirror = mirror(declarationMapper, returnType)
|
||||
val tmpVarName = kniRetVal
|
||||
builder.out("val $tmpVarName = nativeHeap.alloc<${mirror.pointedType.render(kotlinScope)}>()")
|
||||
builder.out("val $tmpVarName = nativeHeap.alloc<${mirror.pointedType.render(builder.scope)}>()")
|
||||
builder.pushBlock("try {", free = "finally { nativeHeap.free($tmpVarName) }")
|
||||
bridgeArguments.add(BridgeTypedKotlinValue(BridgedType.NATIVE_PTR, "$tmpVarName.rawPtr"))
|
||||
BridgedType.VOID
|
||||
@@ -78,7 +77,11 @@ class MappingBridgeGeneratorImpl(
|
||||
if (unwrappedType is RecordType) {
|
||||
nativeValues.add("*(${unwrappedType.decl.spelling}*)${bridgeNativeValues[index]}")
|
||||
} else {
|
||||
nativeValues.add(mirror(declarationMapper, type).info.cFromBridged(bridgeNativeValues[index]))
|
||||
nativeValues.add(
|
||||
mirror(declarationMapper, type).info.cFromBridged(
|
||||
bridgeNativeValues[index], scope, nativeBacked
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +110,7 @@ class MappingBridgeGeneratorImpl(
|
||||
}
|
||||
else -> {
|
||||
val mirror = mirror(declarationMapper, returnType)
|
||||
mirror.info.argFromBridged(callExpr, kotlinScope)
|
||||
mirror.info.argFromBridged(callExpr, builder.scope, nativeBacked)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,12 +162,12 @@ class MappingBridgeGeneratorImpl(
|
||||
nativeValues.forEachIndexed { index, (type, _) ->
|
||||
val mirror = mirror(declarationMapper, type)
|
||||
if (type.unwrapTypedefs() is RecordType) {
|
||||
val pointedTypeName = mirror.pointedType.render(kotlinScope)
|
||||
val pointedTypeName = mirror.pointedType.render(this.scope)
|
||||
kotlinValues.add(
|
||||
"interpretPointed<$pointedTypeName>(${bridgeKotlinValues[index]}).readValue()"
|
||||
)
|
||||
} else {
|
||||
kotlinValues.add(mirror.info.argFromBridged(bridgeKotlinValues[index], kotlinScope))
|
||||
kotlinValues.add(mirror.info.argFromBridged(bridgeKotlinValues[index], this.scope, nativeBacked))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +192,7 @@ class MappingBridgeGeneratorImpl(
|
||||
kniRetVal
|
||||
}
|
||||
else -> {
|
||||
mirror(declarationMapper, returnType).info.cFromBridged(callExpr)
|
||||
mirror(declarationMapper, returnType).info.cFromBridged(callExpr, builder.scope, nativeBacked)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+176
-32
@@ -88,15 +88,15 @@ sealed class TypeMirror(val pointedType: KotlinClassifierType, val info: TypeInf
|
||||
/**
|
||||
* Mirror for C type to be represented in Kotlin as by-value type.
|
||||
*/
|
||||
class ByValue(pointedType: KotlinClassifierType, info: TypeInfo, val valueType: KotlinClassifierType) :
|
||||
TypeMirror(pointedType, info) {
|
||||
class ByValue(
|
||||
pointedType: KotlinClassifierType,
|
||||
info: TypeInfo,
|
||||
val valueType: KotlinType,
|
||||
val nullable: Boolean = (info is TypeInfo.Pointer)
|
||||
) : TypeMirror(pointedType, info) {
|
||||
|
||||
override val argType: KotlinType
|
||||
get() = if ((info is TypeInfo.Pointer || (info is TypeInfo.ObjCPointerInfo && info.type.isNullable))) {
|
||||
valueType.makeNullable()
|
||||
} else {
|
||||
valueType
|
||||
}
|
||||
get() = valueType.makeNullableAsSpecified(nullable)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,11 +119,19 @@ sealed class TypeInfo {
|
||||
/**
|
||||
* The conversion from [bridgedType] to [TypeMirror.argType].
|
||||
*/
|
||||
abstract fun argFromBridged(expr: KotlinExpression, scope: KotlinScope): KotlinExpression
|
||||
abstract fun argFromBridged(
|
||||
expr: KotlinExpression,
|
||||
scope: KotlinScope,
|
||||
nativeBacked: NativeBacked
|
||||
): KotlinExpression
|
||||
|
||||
abstract val bridgedType: BridgedType
|
||||
|
||||
open fun cFromBridged(expr: NativeExpression): NativeExpression = expr
|
||||
open fun cFromBridged(
|
||||
expr: NativeExpression,
|
||||
scope: NativeScope,
|
||||
nativeBacked: NativeBacked
|
||||
): NativeExpression = expr
|
||||
|
||||
open fun cToBridged(expr: NativeExpression): NativeExpression = expr
|
||||
|
||||
@@ -136,7 +144,7 @@ sealed class TypeInfo {
|
||||
class Primitive(override val bridgedType: BridgedType, val varClass: Classifier) : TypeInfo() {
|
||||
|
||||
override fun argToBridged(expr: KotlinExpression) = expr
|
||||
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope) = expr
|
||||
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope, nativeBacked: NativeBacked) = expr
|
||||
|
||||
override fun constructPointedType(valueType: KotlinType) = varClass.typeWith(valueType)
|
||||
}
|
||||
@@ -144,11 +152,13 @@ sealed class TypeInfo {
|
||||
class Boolean : TypeInfo() {
|
||||
override fun argToBridged(expr: KotlinExpression) = "$expr.toByte()"
|
||||
|
||||
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope) = "$expr.toBoolean()"
|
||||
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope, nativeBacked: NativeBacked) =
|
||||
"$expr.toBoolean()"
|
||||
|
||||
override val bridgedType: BridgedType get() = BridgedType.BYTE
|
||||
|
||||
override fun cFromBridged(expr: NativeExpression) = "($expr) ? 1 : 0"
|
||||
override fun cFromBridged(expr: NativeExpression, scope: NativeScope, nativeBacked: NativeBacked) =
|
||||
"($expr) ? 1 : 0"
|
||||
|
||||
override fun cToBridged(expr: NativeExpression) = "($expr) ? 1 : 0"
|
||||
|
||||
@@ -158,7 +168,7 @@ sealed class TypeInfo {
|
||||
class Enum(val clazz: Classifier, override val bridgedType: BridgedType) : TypeInfo() {
|
||||
override fun argToBridged(expr: KotlinExpression) = "$expr.value"
|
||||
|
||||
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope) =
|
||||
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope, nativeBacked: NativeBacked) =
|
||||
scope.reference(clazz) + ".byValue($expr)"
|
||||
|
||||
override fun constructPointedType(valueType: KotlinType) =
|
||||
@@ -169,13 +179,14 @@ sealed class TypeInfo {
|
||||
class Pointer(val pointee: KotlinType) : TypeInfo() {
|
||||
override fun argToBridged(expr: String) = "$expr.rawValue"
|
||||
|
||||
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope) =
|
||||
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope, nativeBacked: NativeBacked) =
|
||||
"interpretCPointer<${pointee.render(scope)}>($expr)"
|
||||
|
||||
override val bridgedType: BridgedType
|
||||
get() = BridgedType.NATIVE_PTR
|
||||
|
||||
override fun cFromBridged(expr: String) = "(void*)$expr" // Note: required for JVM
|
||||
override fun cFromBridged(expr: NativeExpression, scope: NativeScope, nativeBacked: NativeBacked) =
|
||||
"(void*)$expr" // Note: required for JVM
|
||||
|
||||
override fun constructPointedType(valueType: KotlinType) = KotlinTypes.cPointerVarOf.typeWith(valueType)
|
||||
}
|
||||
@@ -183,7 +194,7 @@ sealed class TypeInfo {
|
||||
class ObjCPointerInfo(val kotlinType: KotlinType, val type: ObjCPointer) : TypeInfo() {
|
||||
override fun argToBridged(expr: String) = "$expr.rawPtr"
|
||||
|
||||
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope) =
|
||||
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope, nativeBacked: NativeBacked) =
|
||||
"interpretObjCPointerOrNull<${kotlinType.render(scope)}>($expr)" +
|
||||
if (type.isNullable) "" else "!!"
|
||||
|
||||
@@ -196,8 +207,8 @@ sealed class TypeInfo {
|
||||
class NSString(val type: ObjCPointer) : TypeInfo() {
|
||||
override fun argToBridged(expr: String) = "CreateNSStringFromKString($expr)"
|
||||
|
||||
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope) = "CreateKStringFromNSString($expr)" +
|
||||
if (type.isNullable) "" else "!!"
|
||||
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope, nativeBacked: NativeBacked) =
|
||||
"CreateKStringFromNSString($expr)" + if (type.isNullable) "" else "!!"
|
||||
|
||||
override val bridgedType: BridgedType
|
||||
get() = BridgedType.OBJC_POINTER
|
||||
@@ -207,11 +218,129 @@ sealed class TypeInfo {
|
||||
}
|
||||
}
|
||||
|
||||
class ObjCBlockPointerInfo(val kotlinType: KotlinFunctionType, val type: ObjCBlockPointer) : TypeInfo() {
|
||||
|
||||
override val bridgedType: BridgedType
|
||||
get() = BridgedType.OBJC_POINTER
|
||||
|
||||
// When passing Kotlin function as block pointer from Kotlin to native,
|
||||
// it first gets wrapped by a holder in [argToBridged],
|
||||
// and then converted to block in [cFromBridged].
|
||||
|
||||
override fun argToBridged(expr: KotlinExpression): KotlinExpression = "createKotlinObjectHolder($expr)"
|
||||
|
||||
override fun cFromBridged(
|
||||
expr: NativeExpression,
|
||||
scope: NativeScope,
|
||||
nativeBacked: NativeBacked
|
||||
): NativeExpression {
|
||||
val mappingBridgeGenerator = scope.mappingBridgeGenerator
|
||||
|
||||
val blockParameters = type.parameterTypes.mapIndexed { index, it ->
|
||||
"p$index" to it.getStringRepresentation()
|
||||
}.joinToString { "${it.second} ${it.first}" }
|
||||
|
||||
val blockReturnType = type.returnType.getStringRepresentation()
|
||||
|
||||
val kniFunction = "kniFunction"
|
||||
|
||||
val codeBuilder = NativeCodeBuilder(scope)
|
||||
|
||||
return buildString {
|
||||
append("({ ") // Statement expression begins.
|
||||
append("id $kniFunction = $expr; ") // Note: it gets captured below.
|
||||
append("($kniFunction == nil) ? nil : ")
|
||||
append("(id)") // Cast the block to `id`.
|
||||
append("^$blockReturnType($blockParameters) {") // Block begins.
|
||||
|
||||
// As block body, generate the code which simply bridges to Kotlin and calls the Kotlin function:
|
||||
mappingBridgeGenerator.nativeToKotlin(
|
||||
codeBuilder,
|
||||
nativeBacked,
|
||||
type.returnType,
|
||||
type.parameterTypes.mapIndexed { index, it ->
|
||||
TypedNativeValue(it, "p$index")
|
||||
} + TypedNativeValue(ObjCIdType(ObjCPointer.Nullability.Nullable, emptyList()), kniFunction)
|
||||
) { kotlinValues ->
|
||||
val kotlinFunctionType = kotlinType.render(this.scope)
|
||||
val kotlinFunction = "unwrapKotlinObjectHolder<$kotlinFunctionType>(${kotlinValues.last()})"
|
||||
"$kotlinFunction(${kotlinValues.dropLast(1).joinToString()})"
|
||||
}.let {
|
||||
codeBuilder.out("return $it;")
|
||||
}
|
||||
|
||||
codeBuilder.lines.joinTo(this, separator = " ")
|
||||
|
||||
append(" };") // Block ends.
|
||||
append(" })") // Statement expression ends.
|
||||
}
|
||||
}
|
||||
|
||||
// When passing block pointer as Kotlin function from native to Kotlin,
|
||||
// it is converted to Kotlin function in [cFromBridged].
|
||||
|
||||
override fun cToBridged(expr: NativeExpression): NativeExpression = expr
|
||||
|
||||
override fun argFromBridged(
|
||||
expr: KotlinExpression,
|
||||
scope: KotlinScope,
|
||||
nativeBacked: NativeBacked
|
||||
): KotlinExpression {
|
||||
val mappingBridgeGenerator = scope.mappingBridgeGenerator
|
||||
|
||||
val funParameters = type.parameterTypes.mapIndexed { index, it ->
|
||||
"p$index" to kotlinType.parameterTypes[index]
|
||||
}.joinToString { "${it.first}: ${it.second.render(scope)}" }
|
||||
|
||||
val funReturnType = kotlinType.returnType.render(scope)
|
||||
|
||||
val codeBuilder = KotlinCodeBuilder(scope)
|
||||
val kniBlockPtr = "kniBlockPtr"
|
||||
|
||||
|
||||
// Build the anonymous function expression:
|
||||
val anonymousFun = buildString {
|
||||
append("fun($funParameters): $funReturnType {\n") // Anonymous function begins.
|
||||
|
||||
// As function body, generate the code which simply bridges to native and calls the block:
|
||||
mappingBridgeGenerator.kotlinToNative(
|
||||
codeBuilder,
|
||||
nativeBacked,
|
||||
type.returnType,
|
||||
type.parameterTypes.mapIndexed { index, it ->
|
||||
TypedKotlinValue(it, "p$index")
|
||||
} + TypedKotlinValue(PointerType(VoidType), "interpretCPointer<COpaque>($kniBlockPtr)")
|
||||
|
||||
) { nativeValues ->
|
||||
val type = type
|
||||
val blockType = blockTypeStringRepresentation(type)
|
||||
val objCBlock = "((__bridge $blockType)${nativeValues.last()})"
|
||||
"$objCBlock(${nativeValues.dropLast(1).joinToString()})"
|
||||
}.let {
|
||||
codeBuilder.out("return $it")
|
||||
}
|
||||
|
||||
codeBuilder.build().joinTo(this, separator = "\n")
|
||||
append("}") // Anonymous function ends.
|
||||
}
|
||||
|
||||
val nullOutput = if (type.isNullable) "null" else "throw NullPointerException()"
|
||||
|
||||
return "$expr.let { $kniBlockPtr -> if (kniBlockPtr == nativeNullPtr) $nullOutput else $anonymousFun }"
|
||||
}
|
||||
|
||||
override fun constructPointedType(valueType: KotlinType): KotlinClassifierType {
|
||||
return Classifier.topLevel("kotlinx.cinterop", "ObjCBlockVar").typeWith(valueType)
|
||||
}
|
||||
}
|
||||
|
||||
class ByRef(val pointed: KotlinType) : TypeInfo() {
|
||||
override fun argToBridged(expr: String) = error(pointed)
|
||||
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope) = error(pointed)
|
||||
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope, nativeBacked: NativeBacked) =
|
||||
error(pointed)
|
||||
override val bridgedType: BridgedType get() = error(pointed)
|
||||
override fun cFromBridged(expr: String) = error(pointed)
|
||||
override fun cFromBridged(expr: NativeExpression, scope: NativeScope, nativeBacked: NativeBacked) =
|
||||
error(pointed)
|
||||
override fun cToBridged(expr: String) = error(pointed)
|
||||
|
||||
// TODO: this method must not exist
|
||||
@@ -327,7 +456,8 @@ fun mirror(declarationMapper: DeclarationMapper, type: Type): TypeMirror = when
|
||||
is TypeMirror.ByValue -> TypeMirror.ByValue(
|
||||
Classifier.topLevel(pkg, "${name}Var").type,
|
||||
baseType.info,
|
||||
Classifier.topLevel(pkg, name).type
|
||||
Classifier.topLevel(pkg, name).type,
|
||||
nullable = baseType.nullable
|
||||
)
|
||||
|
||||
is TypeMirror.ByRef -> TypeMirror.ByRef(Classifier.topLevel(pkg, name).type, baseType.info)
|
||||
@@ -343,8 +473,7 @@ fun mirror(declarationMapper: DeclarationMapper, type: Type): TypeMirror = when
|
||||
private fun objCPointerMirror(declarationMapper: DeclarationMapper, type: ObjCPointer): TypeMirror.ByValue {
|
||||
if (type is ObjCObjectPointer && type.def.name == "NSString") {
|
||||
val info = TypeInfo.NSString(type)
|
||||
val valueType = KotlinTypes.string.makeNullableAsSpecified(type.isNullable)
|
||||
return TypeMirror.ByValue(info.constructPointedType(valueType), info, valueType)
|
||||
return objCMirror(KotlinTypes.string, info, type.isNullable)
|
||||
}
|
||||
|
||||
val clazz = when (type) {
|
||||
@@ -353,21 +482,35 @@ private fun objCPointerMirror(declarationMapper: DeclarationMapper, type: ObjCPo
|
||||
is ObjCClassPointer -> KotlinTypes.objCClass
|
||||
is ObjCObjectPointer -> declarationMapper.getKotlinClassFor(type.def)
|
||||
is ObjCInstanceType -> TODO(type.toString()) // Must have already been handled.
|
||||
is ObjCBlockPointer -> return objCBlockPointerMirror(declarationMapper, type)
|
||||
}
|
||||
|
||||
return objCPointerMirror(clazz, type)
|
||||
val valueType = clazz.type
|
||||
return objCMirror(valueType, TypeInfo.ObjCPointerInfo(valueType, type), type.isNullable)
|
||||
}
|
||||
|
||||
private fun objCPointerMirror(clazz: Classifier, type: ObjCPointer): TypeMirror.ByValue {
|
||||
val kotlinType = clazz.type
|
||||
val pointedType = KotlinTypes.objCObjectVar.typeWith(kotlinType.makeNullableAsSpecified(type.isNullable))
|
||||
return TypeMirror.ByValue(
|
||||
pointedType,
|
||||
TypeInfo.ObjCPointerInfo(kotlinType, type),
|
||||
kotlinType
|
||||
private fun objCBlockPointerMirror(declarationMapper: DeclarationMapper, type: ObjCBlockPointer): TypeMirror.ByValue {
|
||||
val returnType = if (type.returnType.unwrapTypedefs() is VoidType) {
|
||||
KotlinTypes.unit
|
||||
} else {
|
||||
mirror(declarationMapper, type.returnType).argType
|
||||
}
|
||||
val kotlinType = KotlinFunctionType(
|
||||
type.parameterTypes.map { mirror(declarationMapper, it).argType },
|
||||
returnType
|
||||
)
|
||||
|
||||
val info = TypeInfo.ObjCBlockPointerInfo(kotlinType, type)
|
||||
return objCMirror(kotlinType, info, type.isNullable)
|
||||
}
|
||||
|
||||
private fun objCMirror(valueType: KotlinType, info: TypeInfo, nullable: Boolean) = TypeMirror.ByValue(
|
||||
info.constructPointedType(valueType.makeNullableAsSpecified(nullable)),
|
||||
info,
|
||||
valueType.makeNullable(), // All typedefs to Objective-C pointers would be nullable for simplicity
|
||||
nullable
|
||||
)
|
||||
|
||||
fun getKotlinFunctionType(declarationMapper: DeclarationMapper, type: FunctionType): KotlinFunctionType {
|
||||
val returnType = if (type.returnType.unwrapTypedefs() is VoidType) {
|
||||
KotlinTypes.unit
|
||||
@@ -376,7 +519,8 @@ fun getKotlinFunctionType(declarationMapper: DeclarationMapper, type: FunctionTy
|
||||
}
|
||||
return KotlinFunctionType(
|
||||
type.parameterTypes.map { mirror(declarationMapper, it).argType },
|
||||
returnType
|
||||
returnType,
|
||||
nullable = false
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -92,7 +92,7 @@ class ObjCMethodStub(stubGenerator: StubGenerator,
|
||||
private val bridgeHeader: String
|
||||
|
||||
init {
|
||||
val bodyGenerator = KotlinCodeBuilder()
|
||||
val bodyGenerator = KotlinCodeBuilder(scope = stubGenerator.kotlinFile)
|
||||
|
||||
val kotlinParameters = mutableListOf<Pair<String, KotlinType>>()
|
||||
val kotlinObjCBridgeParameters = mutableListOf<Pair<String, KotlinType>>()
|
||||
@@ -215,7 +215,7 @@ class ObjCMethodStub(stubGenerator: StubGenerator,
|
||||
|
||||
private fun genImplementationTemplate(stubGenerator: StubGenerator): String = when (container) {
|
||||
is ObjCClassOrProtocol -> {
|
||||
val codeBuilder = NativeCodeBuilder()
|
||||
val codeBuilder = NativeCodeBuilder(stubGenerator.simpleBridgeGenerator.topLevelNativeScope)
|
||||
|
||||
val result = codeBuilder.genMethodImp(stubGenerator, this, method, container)
|
||||
stubGenerator.simpleBridgeGenerator.insertNativeBridge(this, emptyList(), codeBuilder.lines)
|
||||
|
||||
+2
@@ -44,6 +44,8 @@ interface NativeBacked
|
||||
*/
|
||||
interface SimpleBridgeGenerator {
|
||||
|
||||
val topLevelNativeScope: NativeScope
|
||||
|
||||
/**
|
||||
* Generates the expression to convert given Kotlin values to native counterparts, pass through the bridge,
|
||||
* use inside the native code produced by [block] and then return the result back.
|
||||
|
||||
+19
-10
@@ -26,7 +26,8 @@ class SimpleBridgeGeneratorImpl(
|
||||
private val pkgName: String,
|
||||
private val jvmFileClassName: String,
|
||||
private val libraryForCStubs: NativeLibrary,
|
||||
private val kotlinScope: KotlinScope
|
||||
override val topLevelNativeScope: NativeScope,
|
||||
private val topLevelKotlinScope: KotlinScope
|
||||
) : SimpleBridgeGenerator {
|
||||
|
||||
private var nextUniqueId = 0
|
||||
@@ -70,7 +71,7 @@ class SimpleBridgeGeneratorImpl(
|
||||
|
||||
val kotlinFunctionName = "kniBridge${nextUniqueId++}"
|
||||
val kotlinParameters = kotlinValues.withIndex().joinToString {
|
||||
"p${it.index}: ${it.value.type.kotlinType.render(kotlinScope)}"
|
||||
"p${it.index}: ${it.value.type.kotlinType.render(topLevelKotlinScope)}"
|
||||
}
|
||||
|
||||
val callExpr = "$kotlinFunctionName(${kotlinValues.joinToString { it.value }})"
|
||||
@@ -113,7 +114,7 @@ class SimpleBridgeGeneratorImpl(
|
||||
}
|
||||
nativeLines.add(cFunctionHeader + " {")
|
||||
|
||||
buildNativeCodeLines {
|
||||
buildNativeCodeLines(topLevelNativeScope) {
|
||||
val cExpr = block(cFunctionParameters.takeLast(kotlinValues.size).map { (name, _) -> name })
|
||||
if (returnType != BridgedType.VOID) {
|
||||
out("return ($cReturnType)$cExpr;")
|
||||
@@ -131,7 +132,7 @@ class SimpleBridgeGeneratorImpl(
|
||||
}
|
||||
|
||||
nativeLines.add("}")
|
||||
val kotlinReturnType = returnType.kotlinType.render(kotlinScope)
|
||||
val kotlinReturnType = returnType.kotlinType.render(topLevelKotlinScope)
|
||||
kotlinLines.add("private external fun $kotlinFunctionName($kotlinParameters): $kotlinReturnType")
|
||||
|
||||
val nativeBridge = NativeBridge(kotlinLines, nativeLines)
|
||||
@@ -156,7 +157,9 @@ class SimpleBridgeGeneratorImpl(
|
||||
val kotlinParameters = nativeValues.withIndex().map {
|
||||
"p${it.index}" to it.value.type.kotlinType
|
||||
}
|
||||
val joinedKotlinParameters = kotlinParameters.joinToString { "${it.first}: ${it.second.render(kotlinScope)}" }
|
||||
val joinedKotlinParameters = kotlinParameters.joinToString {
|
||||
"${it.first}: ${it.second.render(topLevelKotlinScope)}"
|
||||
}
|
||||
|
||||
val cFunctionParameters = nativeValues.withIndex().map {
|
||||
"p${it.index}" to it.value.type.nativeType
|
||||
@@ -169,10 +172,10 @@ class SimpleBridgeGeneratorImpl(
|
||||
val cFunctionHeader = "$cReturnType $symbolName($joinedCParameters)"
|
||||
|
||||
nativeLines.add("$cFunctionHeader;")
|
||||
val kotlinReturnType = returnType.kotlinType.render(kotlinScope)
|
||||
val kotlinReturnType = returnType.kotlinType.render(topLevelKotlinScope)
|
||||
kotlinLines.add("private fun $kotlinFunctionName($joinedKotlinParameters): $kotlinReturnType {")
|
||||
|
||||
buildKotlinCodeLines {
|
||||
buildKotlinCodeLines(topLevelKotlinScope) {
|
||||
var kotlinExpr = block(kotlinParameters.map { (name, _) -> name })
|
||||
if (returnType == BridgedType.OBJC_POINTER) {
|
||||
// The Kotlin code may lose the ownership on this pointer after returning from the bridge,
|
||||
@@ -207,13 +210,19 @@ class SimpleBridgeGeneratorImpl(
|
||||
nativeBridges.map { it.second.nativeLines }
|
||||
.mapFragmentIsCompilable(libraryForCStubs)
|
||||
.forEachIndexed { index, isCompilable ->
|
||||
if (isCompilable) {
|
||||
includedBridges.add(nativeBridges[index].second)
|
||||
} else {
|
||||
if (!isCompilable) {
|
||||
excludedClients.add(nativeBridges[index].first)
|
||||
}
|
||||
}
|
||||
|
||||
nativeBridges.mapNotNullTo(includedBridges) { (nativeBacked, nativeBridge) ->
|
||||
if (nativeBacked in excludedClients) {
|
||||
null
|
||||
} else {
|
||||
nativeBridge
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: exclude unused bridges.
|
||||
|
||||
return object : NativeBridges {
|
||||
|
||||
+17
-1
@@ -51,6 +51,7 @@ fun Type.getStringRepresentation(): String = when (this) {
|
||||
is ObjCClassPointer -> "Class$protocolQualifier"
|
||||
is ObjCObjectPointer -> "${def.name}$protocolQualifier*"
|
||||
is ObjCInstanceType -> TODO(this.toString()) // Must have already been handled.
|
||||
is ObjCBlockPointer -> "id"
|
||||
}
|
||||
|
||||
else -> throw kotlin.NotImplementedError()
|
||||
@@ -63,4 +64,19 @@ tailrec fun Type.unwrapTypedefs(): Type = if (this is Typedef) {
|
||||
this.def.aliased.unwrapTypedefs()
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
fun blockTypeStringRepresentation(type: ObjCBlockPointer): String {
|
||||
return buildString {
|
||||
append(type.returnType.getStringRepresentation())
|
||||
append("(^)")
|
||||
append("(")
|
||||
val blockParameters = if (type.parameterTypes.isEmpty()) {
|
||||
"void"
|
||||
} else {
|
||||
type.parameterTypes.joinToString { it.getStringRepresentation() }
|
||||
}
|
||||
append(blockParameters)
|
||||
append(")")
|
||||
}
|
||||
}
|
||||
|
||||
+20
-5
@@ -160,7 +160,10 @@ class StubGenerator(
|
||||
|
||||
private val macroConstantsByName = nativeIndex.macroConstants.associateBy { it.name }
|
||||
|
||||
val kotlinFile = KotlinFile(pkgName, namesToBeDeclared = computeNamesToBeDeclared())
|
||||
val kotlinFile = object : KotlinFile(pkgName, namesToBeDeclared = computeNamesToBeDeclared()) {
|
||||
override val mappingBridgeGenerator: MappingBridgeGenerator
|
||||
get() = this@StubGenerator.mappingBridgeGenerator
|
||||
}
|
||||
|
||||
private fun computeNamesToBeDeclared(): MutableList<String> {
|
||||
return mutableListOf<String>().apply {
|
||||
@@ -402,7 +405,8 @@ class StubGenerator(
|
||||
val readBitsExpr =
|
||||
"readBits(this.rawPtr, ${field.offset}, ${field.size}, $signed).${rawType.convertor!!}()"
|
||||
|
||||
out(" get() = ${typeInfo.argFromBridged(readBitsExpr, kotlinFile)}")
|
||||
val getExpr = typeInfo.argFromBridged(readBitsExpr, kotlinFile, object : NativeBacked {})
|
||||
out(" get() = $getExpr")
|
||||
|
||||
val rawValue = typeInfo.argToBridged("value")
|
||||
val setExpr = "writeBits(this.rawPtr, ${field.offset}, ${field.size}, $rawValue.toLong())"
|
||||
@@ -594,7 +598,7 @@ class StubGenerator(
|
||||
init {
|
||||
// TODO: support dumpShims
|
||||
val kotlinParameters = mutableListOf<Pair<String, KotlinType>>()
|
||||
val bodyGenerator = KotlinCodeBuilder()
|
||||
val bodyGenerator = KotlinCodeBuilder(scope = kotlinFile)
|
||||
val bridgeArguments = mutableListOf<TypedKotlinValue>()
|
||||
|
||||
func.parameters.forEachIndexed { index, parameter ->
|
||||
@@ -864,6 +868,7 @@ class StubGenerator(
|
||||
add("UNUSED_PARAMETER") // For constructors.
|
||||
add("MANY_INTERFACES_MEMBER_NOT_IMPLEMENTED") // Workaround for multiple-inherited properties.
|
||||
add("EXTENSION_SHADOWED_BY_MEMBER") // For Objective-C categories represented as extensions.
|
||||
add("REDUNDANT_NULLABLE") // This warning appears due to Obj-C typedef nullability incomplete support.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -976,9 +981,19 @@ class StubGenerator(
|
||||
}
|
||||
|
||||
val simpleBridgeGenerator: SimpleBridgeGenerator =
|
||||
SimpleBridgeGeneratorImpl(platform, pkgName, jvmFileClassName, libraryForCStubs, kotlinFile)
|
||||
SimpleBridgeGeneratorImpl(
|
||||
platform,
|
||||
pkgName,
|
||||
jvmFileClassName,
|
||||
libraryForCStubs,
|
||||
topLevelNativeScope = object : NativeScope {
|
||||
override val mappingBridgeGenerator: MappingBridgeGenerator
|
||||
get() = this@StubGenerator.mappingBridgeGenerator
|
||||
},
|
||||
topLevelKotlinScope = kotlinFile
|
||||
)
|
||||
|
||||
val mappingBridgeGenerator: MappingBridgeGenerator =
|
||||
MappingBridgeGeneratorImpl(declarationMapper, simpleBridgeGenerator, kotlinFile)
|
||||
MappingBridgeGeneratorImpl(declarationMapper, simpleBridgeGenerator)
|
||||
|
||||
}
|
||||
|
||||
@@ -2199,7 +2199,7 @@ if (isMac()) {
|
||||
|
||||
task interop_objc_smoke(type: RunInteropKonanTest) {
|
||||
disabled = (project.testTarget == 'wasm32') // No interop for wasm yet.
|
||||
goldValue = "Hello, World!\nKotlin says: Hello, everybody!\nHello from Kotlin\n2, 1\n" +
|
||||
goldValue = "84\nFoo\nHello, World!\nKotlin says: Hello, everybody!\nHello from Kotlin\n2, 1\n" +
|
||||
"true\ntrue\nDeallocated\nDeallocated\n"
|
||||
|
||||
source = "interop/objc/smoke.kt"
|
||||
|
||||
@@ -25,3 +25,14 @@
|
||||
@end;
|
||||
|
||||
void replacePairElements(id <MutablePair> pair, int first, int second);
|
||||
|
||||
int invoke1(int arg, int (^block)(int)) {
|
||||
return block(arg);
|
||||
}
|
||||
|
||||
void invoke2(void (^block)(void)) {
|
||||
block();
|
||||
}
|
||||
|
||||
int (^getSupplier(int x))(void);
|
||||
Class (^ _Nonnull getClassGetter(NSObject* obj))(void);
|
||||
|
||||
@@ -8,7 +8,17 @@ fun main(args: Array<String>) {
|
||||
}
|
||||
|
||||
fun run() {
|
||||
println(
|
||||
getSupplier(
|
||||
invoke1(42) { it * 2 }
|
||||
)!!()
|
||||
)
|
||||
|
||||
val foo = Foo()
|
||||
|
||||
val classGetter = getClassGetter(foo)
|
||||
invoke2 { println(classGetter()) }
|
||||
|
||||
foo.hello()
|
||||
foo.name = "everybody"
|
||||
foo.helloWithPrinter(object : NSObject(), PrinterProtocol {
|
||||
|
||||
@@ -47,3 +47,13 @@ void replacePairElements(id <MutablePair> pair, int first, int second) {
|
||||
[pair update:0 add:(first - pair.first)];
|
||||
[pair update:1 sub:(pair.second - second)];
|
||||
}
|
||||
|
||||
int (^getSupplier(int x))(void) {
|
||||
return ^{
|
||||
return x;
|
||||
};
|
||||
}
|
||||
|
||||
Class (^ _Nonnull getClassGetter(NSObject* obj))() {
|
||||
return ^{ return obj.class; };
|
||||
}
|
||||
|
||||
@@ -103,6 +103,51 @@ id MissingInitImp(id self, SEL _cmd) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// TODO: rework the interface to reduce the number of virtual calls
|
||||
// in Kotlin_Interop_createKotlinObjectHolder and Kotlin_Interop_unwrapKotlinObjectHolder
|
||||
@interface KotlinObjectHolder : NSObject
|
||||
-(id)initWithRef:(KRef)ref;
|
||||
-(KRef)ref;
|
||||
@end;
|
||||
|
||||
@implementation KotlinObjectHolder {
|
||||
KRef ref_;
|
||||
};
|
||||
|
||||
-(id)initWithRef:(KRef)ref {
|
||||
if (self = [super init]) {
|
||||
UpdateRef(&ref_, ref);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
-(KRef)ref {
|
||||
return ref_;
|
||||
}
|
||||
|
||||
-(void)dealloc {
|
||||
UpdateRef(&ref_, nullptr);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@end;
|
||||
|
||||
id Kotlin_Interop_createKotlinObjectHolder(KRef any) {
|
||||
if (any == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return [[[KotlinObjectHolder alloc] initWithRef:any] autorelease];
|
||||
}
|
||||
|
||||
KRef Kotlin_Interop_unwrapKotlinObjectHolder(id holder) {
|
||||
if (holder == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return [((KotlinObjectHolder*)holder) ref];
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#else // KONAN_OBJC_INTEROP
|
||||
@@ -134,6 +179,16 @@ KBoolean Kotlin_Interop_ObjCEquals(KNativePtr ptr, KNativePtr otherPtr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void* Kotlin_Interop_createKotlinObjectHolder(KRef any) {
|
||||
RuntimeAssert(false, "Objective-C interop is disabled");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
KRef Kotlin_Interop_unwrapKotlinObjectHolder(void* holder) {
|
||||
RuntimeAssert(false, "Objective-C interop is disabled");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif // KONAN_OBJC_INTEROP
|
||||
Reference in New Issue
Block a user