diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt index 49ca11de5e5..254cbbdd4fa 100644 --- a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt @@ -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, typeAttributes: CValue?): 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): List { diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt index a23ba8530a6..de6bf4a2f4b 100644 --- a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt @@ -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, val returnType: Type +) : ObjCPointer() object UnsupportedType : Type \ No newline at end of file diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt index 420a8860297..4c5eb250a67 100644 --- a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt @@ -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 unwrapKotlinObjectHolder(holder: ObjCObject?): T { + return unwrapKotlinObjectHolderImpl(holder!!.rawPtr) as T +} + +@PublishedApi +@SymbolName("Kotlin_Interop_unwrapKotlinObjectHolder") +external internal fun unwrapKotlinObjectHolderImpl(ptr: NativePtr): Any + class ObjCObjectVar(rawPtr: NativePtr) : CVariable(rawPtr) { companion object : CVariable.Type(pointerSize.toLong(), pointerSize) } -class ObjCStringVarOf(rawPtr: NativePtr) : CVariable(rawPtr) { +class ObjCNotImplementedVar(rawPtr: NativePtr) : CVariable(rawPtr) { companion object : CVariable.Type(pointerSize.toLong(), pointerSize) } -var ObjCStringVarOf.value: T +var ObjCNotImplementedVar.value: T get() = TODO() set(value) = TODO() +typealias ObjCStringVarOf = ObjCNotImplementedVar +typealias ObjCBlockVar = ObjCNotImplementedVar + @konan.internal.Intrinsic external fun getReceiverOrSuper(receiver: NativePtr, superClass: NativePtr): COpaquePointer? @Target(AnnotationTarget.CLASS) diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/CodeBuilders.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/CodeBuilders.kt index 45f93d25896..99ed8c918ac 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/CodeBuilders.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/CodeBuilders.kt @@ -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() fun out(line: String): Unit { @@ -24,13 +28,13 @@ class NativeCodeBuilder { } } -inline fun buildNativeCodeLines(block: NativeCodeBuilder.() -> Unit): List { - val builder = NativeCodeBuilder() +inline fun buildNativeCodeLines(scope: NativeScope, block: NativeCodeBuilder.() -> Unit): List { + val builder = NativeCodeBuilder(scope) builder.block() return builder.lines } -class KotlinCodeBuilder { +class KotlinCodeBuilder(val scope: KotlinScope) { private val lines = mutableListOf() private val freeStack = mutableListOf() @@ -69,8 +73,8 @@ class KotlinCodeBuilder { } } -inline fun buildKotlinCodeLines(block: KotlinCodeBuilder.() -> Unit): List { - val builder = KotlinCodeBuilder() +inline fun buildKotlinCodeLines(scope: KotlinScope, block: KotlinCodeBuilder.() -> Unit): List { + val builder = KotlinCodeBuilder(scope) builder.block() return builder.build() } diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/GlobalVariableStub.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/GlobalVariableStub.kt index a2ff61ed0a9..721ad0a9030 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/GlobalVariableStub.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/GlobalVariableStub.kt @@ -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) diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/KotlinCodeModel.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/KotlinCodeModel.kt index abf5b5bda3f..ee1cf02d651 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/KotlinCodeModel.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/KotlinCodeModel.kt @@ -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, 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, - 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 ) : KotlinScope { diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/MappingBridgeGeneratorImpl.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/MappingBridgeGeneratorImpl.kt index 96419ba8e5c..afdd7141524 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/MappingBridgeGeneratorImpl.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/MappingBridgeGeneratorImpl.kt @@ -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) } } diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/Mappings.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/Mappings.kt index 78527bff80d..d27b1a7e1e2 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/Mappings.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/Mappings.kt @@ -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($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 ) } diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt index e70498327e7..09c22c277e2 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/ObjCStubs.kt @@ -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>() val kotlinObjCBridgeParameters = mutableListOf>() @@ -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) diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/SimpleBridgeGenerator.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/SimpleBridgeGenerator.kt index e8fc8ded270..e7030a350d3 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/SimpleBridgeGenerator.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/SimpleBridgeGenerator.kt @@ -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. diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/SimpleBridgeGeneratorImpl.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/SimpleBridgeGeneratorImpl.kt index 5a06d0eba6a..027a067ca6c 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/SimpleBridgeGeneratorImpl.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/SimpleBridgeGeneratorImpl.kt @@ -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 { diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/TypeUtils.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/TypeUtils.kt index dae4149d302..b2e0eed2768 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/TypeUtils.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/TypeUtils.kt @@ -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 -} \ No newline at end of file +} + +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(")") + } +} diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt index af6bf265399..e7870ff007e 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt @@ -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 { return mutableListOf().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>() - val bodyGenerator = KotlinCodeBuilder() + val bodyGenerator = KotlinCodeBuilder(scope = kotlinFile) val bridgeArguments = mutableListOf() 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) } diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index a5981ff92f4..0a0a3f8606c 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -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" diff --git a/backend.native/tests/interop/objc/smoke.h b/backend.native/tests/interop/objc/smoke.h index a0598a6cc38..19007e38c87 100644 --- a/backend.native/tests/interop/objc/smoke.h +++ b/backend.native/tests/interop/objc/smoke.h @@ -25,3 +25,14 @@ @end; void replacePairElements(id 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); diff --git a/backend.native/tests/interop/objc/smoke.kt b/backend.native/tests/interop/objc/smoke.kt index 0464a90d1ee..2be949479fb 100644 --- a/backend.native/tests/interop/objc/smoke.kt +++ b/backend.native/tests/interop/objc/smoke.kt @@ -8,7 +8,17 @@ fun main(args: Array) { } 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 { diff --git a/backend.native/tests/interop/objc/smoke.m b/backend.native/tests/interop/objc/smoke.m index b603b4b4e9b..cce1924ceaa 100644 --- a/backend.native/tests/interop/objc/smoke.m +++ b/backend.native/tests/interop/objc/smoke.m @@ -47,3 +47,13 @@ void replacePairElements(id 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; }; +} diff --git a/runtime/src/main/cpp/ObjCInteropUtils.mm b/runtime/src/main/cpp/ObjCInteropUtils.mm index 8a07aa82106..13b5652a1d8 100644 --- a/runtime/src/main/cpp/ObjCInteropUtils.mm +++ b/runtime/src/main/cpp/ObjCInteropUtils.mm @@ -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 \ No newline at end of file