Support variadic Objective-C methods
This commit is contained in:
committed by
SvyatoslavScherbina
parent
79e36a31cf
commit
e437339d9a
@@ -896,6 +896,7 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
|
||||
|
||||
return ObjCMethod(
|
||||
selector, encoding, parameters, returnType,
|
||||
isVariadic = clang_Cursor_isVariadic(cursor) != 0,
|
||||
isClass = isClass,
|
||||
nsConsumesSelf = hasAttribute(cursor, NS_CONSUMES_SELF),
|
||||
nsReturnsRetained = hasAttribute(cursor, NS_RETURNS_RETAINED),
|
||||
|
||||
+1
-1
@@ -174,7 +174,7 @@ sealed class ObjCClassOrProtocol(val name: String) : ObjCContainer(), TypeDeclar
|
||||
|
||||
data class ObjCMethod(
|
||||
val selector: String, val encoding: String, val parameters: List<Parameter>, private val returnType: Type,
|
||||
val isClass: Boolean, val nsConsumesSelf: Boolean, val nsReturnsRetained: Boolean,
|
||||
val isVariadic: Boolean, val isClass: Boolean, val nsConsumesSelf: Boolean, val nsReturnsRetained: Boolean,
|
||||
val isOptional: Boolean, val isInit: Boolean, val isExplicitlyDesignatedInitializer: Boolean
|
||||
) {
|
||||
|
||||
|
||||
+13
-2
@@ -321,6 +321,17 @@ abstract class KotlinFile(
|
||||
|
||||
}
|
||||
|
||||
data class KotlinParameter(val name: String, val type: KotlinType) {
|
||||
fun render(scope: KotlinScope) = "${name.asSimpleName()}: ${type.render(scope)}"
|
||||
data class KotlinParameter(
|
||||
val name: String,
|
||||
val type: KotlinType,
|
||||
val isVararg: Boolean,
|
||||
val annotations: List<String>
|
||||
) {
|
||||
fun render(scope: KotlinScope) = buildString {
|
||||
annotations.forEach { append("$it ") }
|
||||
if (isVararg) append("vararg ")
|
||||
append(name.asSimpleName())
|
||||
append(": ")
|
||||
append(type.render(scope))
|
||||
}
|
||||
}
|
||||
|
||||
+49
-35
@@ -25,29 +25,29 @@ private fun ObjCMethod.getKotlinParameterNames(forConstructorOrFactory: Boolean
|
||||
|
||||
val result = mutableListOf<String>()
|
||||
|
||||
fun String.mangled(): String {
|
||||
var mangled = this
|
||||
while (mangled in result) {
|
||||
mangled = "_$mangled"
|
||||
}
|
||||
return mangled
|
||||
}
|
||||
|
||||
// The names of all parameters except first must depend only on the selector:
|
||||
this.parameters.forEachIndexed { index, _ ->
|
||||
if (index > 0) {
|
||||
var name = selectorParts[index]
|
||||
if (name.isEmpty()) {
|
||||
name = "_$index"
|
||||
}
|
||||
|
||||
while (name in result) {
|
||||
name = "_$name"
|
||||
}
|
||||
|
||||
result.add(name)
|
||||
val name = selectorParts[index].takeIf { it.isNotEmpty() } ?: "_$index"
|
||||
result.add(name.mangled())
|
||||
}
|
||||
}
|
||||
|
||||
this.parameters.firstOrNull()?.let {
|
||||
var name = this.getFirstKotlinParameterNameCandidate(forConstructorOrFactory)
|
||||
val name = this.getFirstKotlinParameterNameCandidate(forConstructorOrFactory)
|
||||
result.add(0, name.mangled())
|
||||
}
|
||||
|
||||
while (name in result) {
|
||||
name = "_$name"
|
||||
}
|
||||
result.add(0, name)
|
||||
if (this.isVariadic) {
|
||||
result.add("args".mangled())
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -65,6 +65,32 @@ private fun ObjCMethod.getFirstKotlinParameterNameCandidate(forConstructorOrFact
|
||||
return this.parameters.first().name?.takeIf { it.isNotEmpty() } ?: "_0"
|
||||
}
|
||||
|
||||
private fun ObjCMethod.getKotlinParameters(
|
||||
stubGenerator: StubGenerator,
|
||||
forConstructorOrFactory: Boolean
|
||||
): List<KotlinParameter> {
|
||||
val names = getKotlinParameterNames(forConstructorOrFactory) // TODO: consider refactoring.
|
||||
val result = mutableListOf<KotlinParameter>()
|
||||
|
||||
this.parameters.mapIndexedTo(result) { index, it ->
|
||||
val kotlinType = stubGenerator.mirror(it.type).argType
|
||||
val name = names[index]
|
||||
val annotations = if (it.nsConsumed) listOf("@CCall.Consumed") else emptyList()
|
||||
KotlinParameter(name, kotlinType, isVararg = false, annotations = annotations)
|
||||
}
|
||||
|
||||
if (this.isVariadic) {
|
||||
result += KotlinParameter(
|
||||
names.last(),
|
||||
KotlinTypes.any.makeNullable(),
|
||||
isVararg = true,
|
||||
annotations = emptyList()
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
class ObjCMethodStub(private val stubGenerator: StubGenerator,
|
||||
val method: ObjCMethod,
|
||||
private val container: ObjCContainer,
|
||||
@@ -80,17 +106,15 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
|
||||
// TODO: generate only constructor/factory in this case.
|
||||
val kotlinScope = stubGenerator.kotlinFile
|
||||
|
||||
val newParameterNames = method.getKotlinParameterNames(forConstructorOrFactory = true)
|
||||
val parameters = kotlinParameters.zip(newParameterNames) { parameter, newName ->
|
||||
KotlinParameter(newName, parameter.type)
|
||||
}.renderParameters(kotlinScope)
|
||||
val parameters = method.getKotlinParameters(stubGenerator, forConstructorOrFactory = true)
|
||||
.renderParameters(kotlinScope)
|
||||
|
||||
when (container) {
|
||||
is ObjCClass -> {
|
||||
result.add(0,
|
||||
deprecatedInit(
|
||||
container.kotlinClassName(method.isClass),
|
||||
kotlinParameters.map { it.name },
|
||||
kotlinMethodParameters.map { it.name },
|
||||
factory = false
|
||||
)
|
||||
)
|
||||
@@ -113,7 +137,7 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
|
||||
result.add(0,
|
||||
deprecatedInit(
|
||||
className,
|
||||
kotlinParameters.map { it.name },
|
||||
kotlinMethodParameters.map { it.name },
|
||||
factory = true
|
||||
)
|
||||
)
|
||||
@@ -145,29 +169,19 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
|
||||
)
|
||||
}
|
||||
|
||||
private val kotlinParameters: List<KotlinParameter>
|
||||
private val kotlinMethodParameters: List<KotlinParameter>
|
||||
private val kotlinReturnType: String
|
||||
private val header: String
|
||||
internal val objCMethodAnnotations: List<String>
|
||||
private val isStret: Boolean
|
||||
|
||||
init {
|
||||
kotlinParameters = mutableListOf()
|
||||
kotlinMethodParameters = method.getKotlinParameters(stubGenerator, forConstructorOrFactory = false)
|
||||
|
||||
val returnType = method.getReturnType(container.classOrProtocol)
|
||||
|
||||
isStret = returnType.isStret(stubGenerator.configuration.target)
|
||||
|
||||
val kotlinParameterNames = method.getKotlinParameterNames()
|
||||
|
||||
method.parameters.forEachIndexed { index, it ->
|
||||
val name = kotlinParameterNames[index]
|
||||
|
||||
val kotlinType = stubGenerator.mirror(it.type).argType
|
||||
val annotatedName = if (it.nsConsumed) "@CCall.Consumed $name" else name
|
||||
kotlinParameters.add(KotlinParameter(annotatedName, kotlinType))
|
||||
}
|
||||
|
||||
this.kotlinReturnType = if (returnType.unwrapTypedefs() is VoidType) {
|
||||
KotlinTypes.unit
|
||||
} else {
|
||||
@@ -176,7 +190,7 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
|
||||
|
||||
objCMethodAnnotations = buildObjCMethodAnnotations("@ObjCMethod")
|
||||
|
||||
val joinedKotlinParameters = kotlinParameters.renderParameters(stubGenerator.kotlinFile)
|
||||
val joinedKotlinParameters = kotlinMethodParameters.renderParameters(stubGenerator.kotlinFile)
|
||||
|
||||
this.header = buildString {
|
||||
if (container !is ObjCProtocol) append("external ")
|
||||
@@ -229,7 +243,7 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
|
||||
private fun deprecatedInit(className: String, initParameterNames: List<String>, factory: Boolean): String {
|
||||
val replacement = if (factory) "$className.create" else className
|
||||
val replacementKind = if (factory) "factory method" else "constructor"
|
||||
val replaceWith = "$replacement(${initParameterNames.joinToString()})"
|
||||
val replaceWith = "$replacement(${initParameterNames.joinToString { it.asSimpleName() }})"
|
||||
|
||||
return deprecated("Use $replacementKind instead", replaceWith)
|
||||
}
|
||||
|
||||
+49
-35
@@ -57,7 +57,8 @@ internal interface KotlinStubs {
|
||||
|
||||
private class KotlinToCCallBuilder(
|
||||
val irBuilder: IrBuilderWithScope,
|
||||
val stubs: KotlinStubs
|
||||
val stubs: KotlinStubs,
|
||||
val isObjCMethod: Boolean
|
||||
) {
|
||||
|
||||
val cBridgeName = stubs.getUniqueCName("knbridge")
|
||||
@@ -108,7 +109,7 @@ private fun KotlinToCCallBuilder.buildKotlinBridgeCall(transformCall: (IrCall) -
|
||||
internal fun KotlinStubs.generateCCall(expression: IrCall, builder: IrBuilderWithScope, isInvoke: Boolean): IrExpression {
|
||||
require(expression.dispatchReceiver == null)
|
||||
|
||||
val callBuilder = KotlinToCCallBuilder(builder, this)
|
||||
val callBuilder = KotlinToCCallBuilder(builder, this, isObjCMethod = false)
|
||||
|
||||
val callee = expression.symbol.owner as IrSimpleFunction
|
||||
|
||||
@@ -138,27 +139,10 @@ internal fun KotlinStubs.generateCCall(expression: IrCall, builder: IrBuilderWit
|
||||
targetPtrParameter = null
|
||||
targetFunctionName = this.getUniqueCName("target")
|
||||
|
||||
(0 until expression.valueArgumentsCount).forEach { index ->
|
||||
val parameter = callee.valueParameters[index]
|
||||
val argument = expression.getValueArgument(index)
|
||||
if (parameter.isVararg) {
|
||||
require(index == expression.valueArgumentsCount - 1)
|
||||
require(argument is IrVararg?)
|
||||
argument?.elements.orEmpty().forEach {
|
||||
when (it) {
|
||||
is IrExpression -> callBuilder.addArgument(it, it.type, variadic = true, parameter = null)
|
||||
|
||||
is IrSpreadElement ->
|
||||
reportError(it, "spread operator is not supported for variadic C functions")
|
||||
|
||||
else -> error(it)
|
||||
}
|
||||
}
|
||||
callBuilder.cFunctionBuilder.variadic = true
|
||||
} else {
|
||||
callBuilder.addArgument(argument!!, parameter.type, variadic = false, parameter = parameter)
|
||||
}
|
||||
val arguments = (0 until expression.valueArgumentsCount).map {
|
||||
expression.getValueArgument(it)
|
||||
}
|
||||
callBuilder.addArguments(arguments, callee)
|
||||
}
|
||||
|
||||
val returnValuePassing = if (isInvoke) {
|
||||
@@ -184,6 +168,30 @@ internal fun KotlinStubs.generateCCall(expression: IrCall, builder: IrBuilderWit
|
||||
return result
|
||||
}
|
||||
|
||||
private fun KotlinToCCallBuilder.addArguments(arguments: List<IrExpression?>, callee: IrFunction) {
|
||||
arguments.forEachIndexed { index, argument ->
|
||||
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)
|
||||
}
|
||||
}
|
||||
cFunctionBuilder.variadic = true
|
||||
} else {
|
||||
addArgument(argument!!, parameter.type, variadic = false, parameter = parameter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun KotlinToCCallBuilder.emitCBridge() {
|
||||
val cLines = mutableListOf<String>()
|
||||
|
||||
@@ -210,10 +218,10 @@ internal fun KotlinStubs.generateObjCCall(
|
||||
call: IrFunctionAccessExpression,
|
||||
superQualifier: IrClassSymbol?,
|
||||
receiver: IrExpression,
|
||||
arguments: List<IrExpression>
|
||||
arguments: List<IrExpression?>
|
||||
): IrExpression {
|
||||
|
||||
val callBuilder = KotlinToCCallBuilder(builder, this)
|
||||
val callBuilder = KotlinToCCallBuilder(builder, this, isObjCMethod = true)
|
||||
|
||||
val superClass = superQualifier?.let { builder.getObjCClass(symbols, it) }
|
||||
?: builder.irNullNativePtr(symbols)
|
||||
@@ -257,15 +265,7 @@ internal fun KotlinStubs.generateObjCCall(
|
||||
callBuilder.cCallBuilder.arguments += "@selector($selector)"
|
||||
callBuilder.cFunctionBuilder.addParameter(CTypes.voidPtr)
|
||||
|
||||
(0 until method.valueParameters.size).forEach {
|
||||
val parameter = method.valueParameters[it]
|
||||
callBuilder.addArgument(
|
||||
arguments[it],
|
||||
type = parameter.type,
|
||||
variadic = false,
|
||||
parameter = parameter
|
||||
)
|
||||
}
|
||||
callBuilder.addArguments(arguments, method)
|
||||
|
||||
val returnValuePassing =
|
||||
mapReturnType(method.returnType, TypeLocation.FunctionCallResult(call), signature = method)
|
||||
@@ -327,6 +327,14 @@ private fun CCallbackBuilder.addParameter(it: IrValueParameter, functionParamete
|
||||
TypeLocation.FunctionPointerParameter(cFunctionBuilder.numberOfParameters, location)
|
||||
}
|
||||
|
||||
if (functionParameter.isVararg) {
|
||||
stubs.reportError(typeLocation.element, if (isObjCMethod) {
|
||||
"overriding variadic Objective-C methods is not supported"
|
||||
} else {
|
||||
"variadic function pointers are not supported"
|
||||
})
|
||||
}
|
||||
|
||||
val valuePassing = stubs.mapType(
|
||||
it.type,
|
||||
retained = it.isConsumed(),
|
||||
@@ -548,8 +556,14 @@ private fun KotlinToCCallBuilder.mapParameter(
|
||||
classifier == symbols.interopCValues || // Note: this should not be accepted, but is required for compatibility
|
||||
classifier == symbols.interopCValuesRef -> CValuesRefArgumentPassing
|
||||
|
||||
classifier == symbols.string && (variadic || parameter?.isCStringParameter() == true) ->
|
||||
classifier == symbols.string && (variadic || parameter?.isCStringParameter() == true) -> {
|
||||
if (variadic && isObjCMethod) {
|
||||
stubs.reportError(argument, "Passing String as variadic Objective-C argument is ambiguous; " +
|
||||
"cast it to NSString or pass with '.cstr' as C string")
|
||||
// TODO: consider reporting a warning for C functions.
|
||||
}
|
||||
CStringArgumentPassing()
|
||||
}
|
||||
|
||||
classifier == symbols.string && parameter?.isWCStringParameter() == true ->
|
||||
WCStringArgumentPassing()
|
||||
@@ -1177,7 +1191,7 @@ private class ObjCBlockPointerValuePassing(
|
||||
}
|
||||
|
||||
private fun IrBuilderWithScope.callBlock(blockPtr: IrExpression, arguments: List<IrExpression>): IrExpression {
|
||||
val callBuilder = KotlinToCCallBuilder(this, stubs)
|
||||
val callBuilder = KotlinToCCallBuilder(this, stubs, isObjCMethod = false)
|
||||
|
||||
val rawBlockPointerParameter = callBuilder.passThroughBridge(blockPtr, blockPtr.type, CTypes.id)
|
||||
val blockVariableName = "block"
|
||||
|
||||
+6
-6
@@ -596,7 +596,7 @@ internal class InteropLoweringPart1(val context: Context) : BaseInteropIrTransfo
|
||||
initMethodInfo,
|
||||
superQualifier = expression.symbol.owner.constructedClass.symbol,
|
||||
receiver = builder.getRawPtr(builder.irGet(constructedClass.thisReceiver!!)),
|
||||
arguments = initMethod.valueParameters.map { expression.getValueArgument(it.index)!! },
|
||||
arguments = initMethod.valueParameters.map { expression.getValueArgument(it.index) },
|
||||
call = expression,
|
||||
method = initMethod
|
||||
)
|
||||
@@ -629,7 +629,7 @@ internal class InteropLoweringPart1(val context: Context) : BaseInteropIrTransfo
|
||||
info: ObjCMethodInfo,
|
||||
superQualifier: IrClassSymbol?,
|
||||
receiver: IrExpression,
|
||||
arguments: List<IrExpression>,
|
||||
arguments: List<IrExpression?>,
|
||||
call: IrFunctionAccessExpression,
|
||||
method: IrSimpleFunction
|
||||
): IrExpression = generateWithStubs(call) {
|
||||
@@ -655,7 +655,7 @@ internal class InteropLoweringPart1(val context: Context) : BaseInteropIrTransfo
|
||||
val initMethod = callee.getObjCInitMethod()
|
||||
|
||||
if (initMethod != null) {
|
||||
val arguments = descriptor.valueParameters.map { expression.getValueArgument(it)!! }
|
||||
val arguments = descriptor.valueParameters.map { expression.getValueArgument(it) }
|
||||
assert(expression.extensionReceiver == null)
|
||||
assert(expression.dispatchReceiver == null)
|
||||
|
||||
@@ -670,7 +670,7 @@ internal class InteropLoweringPart1(val context: Context) : BaseInteropIrTransfo
|
||||
|
||||
descriptor.getObjCFactoryInitMethodInfo()?.let { initMethodInfo ->
|
||||
val arguments = (0 until expression.valueArgumentsCount)
|
||||
.map { index -> expression.getValueArgument(index)!! }
|
||||
.map { index -> expression.getValueArgument(index) }
|
||||
|
||||
return builder.at(expression).run {
|
||||
val classPtr = getRawPtr(expression.extensionReceiver!!)
|
||||
@@ -688,7 +688,7 @@ internal class InteropLoweringPart1(val context: Context) : BaseInteropIrTransfo
|
||||
builder.scope.scopeOwner.annotations.hasAnnotation(FqName("kotlin.native.internal.ExportForCppRuntime"))
|
||||
|
||||
if (!useKotlinDispatch) {
|
||||
val arguments = descriptor.valueParameters.map { expression.getValueArgument(it)!! }
|
||||
val arguments = descriptor.valueParameters.map { expression.getValueArgument(it) }
|
||||
assert(expression.dispatchReceiver == null || expression.extensionReceiver == null)
|
||||
|
||||
if (expression.superQualifier?.isObjCMetaClass() == true) {
|
||||
@@ -773,7 +773,7 @@ internal class InteropLoweringPart1(val context: Context) : BaseInteropIrTransfo
|
||||
private fun IrBuilderWithScope.callAllocAndInit(
|
||||
classPtr: IrExpression,
|
||||
initMethodInfo: ObjCMethodInfo,
|
||||
arguments: List<IrExpression>,
|
||||
arguments: List<IrExpression?>,
|
||||
call: IrFunctionAccessExpression,
|
||||
initMethod: IrSimpleFunction
|
||||
): IrExpression = irBlock(startOffset, endOffset) {
|
||||
|
||||
@@ -144,4 +144,14 @@ NSObject* createNSObject() {
|
||||
|
||||
@property (class) void (^nullBlock)(void);
|
||||
@property (class) void (^notNullBlock)(void);
|
||||
@end;
|
||||
|
||||
@interface TestVarargs : NSObject
|
||||
-(instancetype _Nonnull)initWithFormat:(NSString*)format, ...;
|
||||
+(instancetype _Nonnull)testVarargsWithFormat:(NSString*)format, ...;
|
||||
@property NSString* formatted;
|
||||
@end;
|
||||
|
||||
@interface TestVarargs (TestVarargsExtension)
|
||||
-(instancetype _Nonnull)initWithFormat:(NSString*)format, ...;
|
||||
@end;
|
||||
@@ -21,6 +21,7 @@ fun run() {
|
||||
testExceptions()
|
||||
testBlocks()
|
||||
testCustomRetain()
|
||||
testVarargs()
|
||||
|
||||
assertEquals(2, ForwardDeclaredEnum.TWO.value)
|
||||
|
||||
@@ -256,6 +257,32 @@ fun testCustomRetain() {
|
||||
assertFalse(unexpectedDeallocation)
|
||||
}
|
||||
|
||||
fun testVarargs() {
|
||||
assertEquals(
|
||||
"a b -1",
|
||||
TestVarargs.testVarargsWithFormat(
|
||||
"%@ %s %d",
|
||||
"a" as NSString, "b".cstr, (-1).toByte()
|
||||
).formatted
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
"2 3 9223372036854775807",
|
||||
TestVarargs(
|
||||
"%d %d %lld",
|
||||
2.toShort(), 3, Long.MAX_VALUE
|
||||
).formatted
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
"0.1 0.2 1 0",
|
||||
TestVarargs.create(
|
||||
"%.1f %.1lf %d %d",
|
||||
0.1.toFloat(), 0.2, true, false
|
||||
).formatted
|
||||
)
|
||||
}
|
||||
|
||||
private class MyException : Throwable()
|
||||
|
||||
fun nsArrayOf(vararg elements: Any): NSArray = NSMutableArray().apply {
|
||||
|
||||
@@ -119,4 +119,29 @@ static CustomRetainMethodsImpl* retainedCustomRetainMethodsImpl;
|
||||
return ^{};
|
||||
}
|
||||
|
||||
@end;
|
||||
|
||||
@implementation TestVarargs
|
||||
-(instancetype _Nonnull)initWithFormat:(NSString*)format, ... {
|
||||
self = [super init];
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
self.formatted = [[NSString alloc] initWithFormat:format arguments:args];
|
||||
va_end(args);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
+(instancetype _Nonnull)testVarargsWithFormat:(NSString*)format, ... {
|
||||
TestVarargs* result = [[TestVarargs alloc] init];
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
result.formatted = [[NSString alloc] initWithFormat:format arguments:args];
|
||||
va_end(args);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@end;
|
||||
Reference in New Issue
Block a user