Support *arrayOf(...) for variadic C/Objective-C functions

To be used with named arguments, which may be required to resolve ambiguity.
This commit is contained in:
Svyatoslav Scherbina
2019-04-18 09:55:46 +03:00
committed by SvyatoslavScherbina
parent e437339d9a
commit 39e31d7086
3 changed files with 108 additions and 12 deletions
@@ -53,6 +53,7 @@ internal interface KotlinStubs {
fun getUniqueKotlinFunctionReferenceClassName(prefix: String): String
fun reportError(location: IrElement, message: String): Nothing
fun throwCompilerError(element: IrElement?, message: String): Nothing
}
private class KotlinToCCallBuilder(
@@ -173,18 +174,7 @@ private fun KotlinToCCallBuilder.addArguments(arguments: List<IrExpression?>, ca
val parameter = callee.valueParameters[index]
if (parameter.isVararg) {
require(index == arguments.lastIndex)
require(argument is IrVararg?)
argument?.elements.orEmpty().forEach {
when (it) {
is IrExpression -> addArgument(it, it.type, variadic = true, parameter = null)
is IrSpreadElement ->
stubs.reportError(it, "spread operator is not supported for variadic " +
if (isObjCMethod) "Objective-C methods" else "C functions")
else -> error(it)
}
}
addVariadicArguments(argument)
cFunctionBuilder.variadic = true
} else {
addArgument(argument!!, parameter.type, variadic = false, parameter = parameter)
@@ -192,6 +182,92 @@ private fun KotlinToCCallBuilder.addArguments(arguments: List<IrExpression?>, ca
}
}
private fun KotlinToCCallBuilder.addVariadicArguments(
argumentForVarargParameter: IrExpression?
) = handleArgumentForVarargParameter(argumentForVarargParameter) { variable, elements ->
if (variable == null) {
unwrapVariadicArguments(elements).forEach {
addArgument(it, it.type, variadic = true, parameter = null)
}
} else {
// See comment in [handleArgumentForVarargParameter].
// Array for this vararg parameter is already computed before the call,
// so query statically known typed arguments from this array.
with(irBuilder) {
val argumentTypes = unwrapVariadicArguments(elements).map { it.type }
argumentTypes.forEachIndexed { index, type ->
val untypedArgument = irCall(symbols.arrayGet[symbols.array]!!.owner).apply {
dispatchReceiver = irGet(variable)
putValueArgument(0, irInt(index))
}
val argument = irAs(untypedArgument, type) // Note: this cast always succeeds.
addArgument(argument, type, variadic = true, parameter = null)
}
}
}
}
private fun KotlinToCCallBuilder.unwrapVariadicArguments(
elements: List<IrVarargElement>
): List<IrExpression> = elements.flatMap {
when (it) {
is IrExpression -> listOf(it)
is IrSpreadElement -> {
val expression = it.expression
if (expression is IrCall && expression.symbol == symbols.arrayOf) {
handleArgumentForVarargParameter(expression.getValueArgument(0)) { _, elements ->
unwrapVariadicArguments(elements)
}
} else {
stubs.reportError(it, "When calling variadic " +
if (isObjCMethod) "Objective-C methods" else "C functions " +
"spread operator is supported only for *arrayOf(...)")
}
}
else -> stubs.throwCompilerError(it, "unexpected IrVarargElement")
}
}
private fun <R> KotlinToCCallBuilder.handleArgumentForVarargParameter(
argument: IrExpression?,
block: (variable: IrVariable?, elements: List<IrVarargElement>) -> R
): R = when (argument) {
null -> block(null, emptyList())
is IrVararg -> block(null, argument.elements)
is IrGetValue -> {
/* This is possible when using named arguments with reordering, i.e.
*
* foo(second = *arrayOf(...), first = ...)
*
* psi2ir generates as
*
* val secondTmp = *arrayOf(...)
* val firstTmp = ...
* foo(firstTmp, secondTmp)
*
*
**/
val variable = argument.symbol.owner
if (variable is IrVariable && variable.origin == IrDeclarationOrigin.IR_TEMPORARY_VARIABLE && !variable.isVar) {
val initializer = variable.initializer
if (initializer is IrVararg) {
block(variable, initializer.elements)
} else {
stubs.throwCompilerError(initializer, "unexpected initializer")
}
} else {
stubs.throwCompilerError(variable, "unexpected value declaration")
}
}
else -> stubs.throwCompilerError(argument, "unexpected vararg")
}
private fun KotlinToCCallBuilder.emitCBridge() {
val cLines = mutableListOf<String>()
@@ -85,6 +85,10 @@ internal abstract class BaseInteropIrTransformer(private val context: Context) :
override fun reportError(location: IrElement, message: String): Nothing =
context.reportCompilationError(message, irFile, location)
override fun throwCompilerError(element: IrElement?, message: String): Nothing {
error(irFile, element, message)
}
}
}
@@ -281,6 +281,22 @@ fun testVarargs() {
0.1.toFloat(), 0.2, true, false
).formatted
)
assertEquals(
"1 2 3",
TestVarargs(
format = "%d %d %d",
args = *arrayOf(1, 2, 3)
).formatted
)
assertEquals(
"4 5 6",
TestVarargs(
args = *arrayOf(4, *arrayOf(5, 6)),
format = "%d %d %d"
).formatted
)
}
private class MyException : Throwable()