Represent Objective-C block pointers in methods as Kotlin functions

Also do some refactoring.
This commit is contained in:
Svyatoslav Scherbina
2017-10-16 17:22:26 +03:00
committed by SvyatoslavScherbina
parent caf6719fc4
commit e8f97b0436
18 changed files with 412 additions and 80 deletions
@@ -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> {
@@ -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)
@@ -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()
}
@@ -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)
@@ -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 {
@@ -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)
}
}
@@ -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
)
}
@@ -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)
@@ -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.
@@ -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 {
@@ -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(")")
}
}
@@ -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)
}
+1 -1
View File
@@ -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"
+11
View File
@@ -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 {
+10
View File
@@ -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; };
}
+55
View File
@@ -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