diff --git a/INTEROP.md b/INTEROP.md index 7eac92f473e..449d88162c1 100644 --- a/INTEROP.md +++ b/INTEROP.md @@ -275,7 +275,9 @@ methods available: ### Callbacks ### To convert Kotlin function to pointer to C function, -`staticCFunction(::kotlinFunction)` can be used. +`staticCFunction(::kotlinFunction)` can be used. It is also allowed to provide +the lambda instead of function reference. The function or lambda must not +capture any values. Note that some function types are not supported currently. For example, it is not possible to get pointer to function that receives or returns structs diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt index eca45e429dc..7a8df4d5ae6 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt @@ -277,16 +277,17 @@ private class InteropTransformer(val context: Context, val irFile: IrFile) : IrB } in interop.staticCFunction -> { - val argument = expression.getValueArgument(0)!! - if (argument !is IrCallableReference || argument.getArguments().isNotEmpty()) { + val irCallableReference = unwrapStaticFunctionArgument(expression.getValueArgument(0)!!) + + if (irCallableReference == null || irCallableReference.getArguments().isNotEmpty()) { context.reportCompilationError( - "${descriptor.fqNameSafe} must take an unbound, non-capturing function", + "${descriptor.fqNameSafe} must take an unbound, non-capturing function or lambda", irFile, expression ) // TODO: should probably be reported during analysis. } - val target = argument.descriptor.original + val target = irCallableReference.descriptor.original val signatureTypes = target.allParameters.map { it.type } + target.returnType!! signatureTypes.forEachIndexed { index, type -> @@ -385,6 +386,35 @@ private class InteropTransformer(val context: Context, val irFile: IrFile) : IrB reportError("Type $this is not supported in callback signature") } + private fun unwrapStaticFunctionArgument(argument: IrExpression): IrCallableReference? { + if (argument is IrCallableReference) { + return argument + } + + // Otherwise check whether it is a lambda: + + // 1. It is a container with two statements and expected origin: + + if (argument !is IrContainerExpression || argument.statements.size != 2) { + return null + } + if (argument.origin != IrStatementOrigin.LAMBDA && argument.origin != IrStatementOrigin.ANONYMOUS_FUNCTION) { + return null + } + + // 2. First statement is an empty container (created during local functions lowering): + + val firstStatement = argument.statements.first() + + if (firstStatement !is IrContainerExpression || firstStatement.statements.size != 0) { + return null + } + + // 3. Second statement is IrCallableReference: + + return argument.statements.last() as? IrCallableReference + } + private fun IrCall.getSingleTypeArgument(): KotlinType { val typeParameter = descriptor.original.typeParameters.single() return getTypeArgument(typeParameter)!! diff --git a/backend.native/tests/interop/basics/3.kt b/backend.native/tests/interop/basics/3.kt index f08d45bc285..1d957758697 100644 --- a/backend.native/tests/interop/basics/3.kt +++ b/backend.native/tests/interop/basics/3.kt @@ -11,7 +11,12 @@ fun main(args: Array) { values[3] = 13 values[4] = 8 - cstdlib.qsort(values, count.toLong(), IntVar.size, staticCFunction(::comparator)) + cstdlib.qsort(values, count.toLong(), IntVar.size, staticCFunction { a, b -> + val aValue = a!!.reinterpret()[0] + val bValue = b!!.reinterpret()[0] + + (aValue - bValue) + }) for (i in 0 .. count - 1) { print(values[i]) @@ -20,10 +25,3 @@ fun main(args: Array) { println() } } - -private fun comparator(a: COpaquePointer?, b: COpaquePointer?): Int { - val aValue = a!!.reinterpret()[0] - val bValue = b!!.reinterpret()[0] - - return (aValue - bValue) -}