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 0843d1191c9..6ae7a9cb46f 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 @@ -68,6 +68,13 @@ private fun ObjCMethod.getKotlinParameters( stubIrBuilder: StubsBuildingContext, forConstructorOrFactory: Boolean ): List { + if (this.isInit && this.parameters.isEmpty() && this.selector != "init") { + // Create synthetic Unit parameter, just like Swift does in this case: + val parameterName = this.selector.removePrefix("init").removePrefix("With").decapitalize() + return listOf(FunctionParameterStub(parameterName, WrapperStubType(KotlinTypes.unit))) + // Note: this parameter is explicitly handled in compiler. + } + val names = getKotlinParameterNames(forConstructorOrFactory) // TODO: consider refactoring. val result = mutableListOf() diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGen.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGen.kt index bb04c435e5b..e81748cf77f 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGen.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/cgen/CBridgeGen.kt @@ -87,7 +87,7 @@ private fun KotlinToCCallBuilder.addArgument( variadic: Boolean, parameter: IrValueParameter? ) { - val argumentPassing = mapParameter(type, variadic, parameter, argument) + val argumentPassing = mapCalleeFunctionParameter(type, variadic, parameter, argument) addArgument(argument, argumentPassing, variadic) } @@ -96,7 +96,7 @@ private fun KotlinToCCallBuilder.addArgument( argumentPassing: KotlinToCArgumentPassing, variadic: Boolean ) { - val cArgument = with(argumentPassing) { passValue(argument) } + val cArgument = with(argumentPassing) { passValue(argument) } ?: return cCallBuilder.arguments += cArgument.expression if (!variadic) cFunctionBuilder.addParameter(cArgument.type) } @@ -413,7 +413,7 @@ private fun CCallbackBuilder.addParameter(it: IrValueParameter, functionParamete }) } - val valuePassing = stubs.mapType( + val valuePassing = stubs.mapFunctionParameterType( it.type, retained = it.isConsumed(), variadic = false, @@ -615,7 +615,7 @@ private fun cBoolType(target: KonanTarget): CType? = when (target.family) { else -> CTypes.signedChar } -private fun KotlinToCCallBuilder.mapParameter( +private fun KotlinToCCallBuilder.mapCalleeFunctionParameter( type: IrType, variadic: Boolean, parameter: IrValueParameter?, @@ -638,7 +638,7 @@ private fun KotlinToCCallBuilder.mapParameter( classifier == symbols.string && parameter?.isWCStringParameter() == true -> WCStringArgumentPassing() - else -> stubs.mapType( + else -> stubs.mapFunctionParameterType( type, retained = parameter?.isConsumed() ?: false, variadic = variadic, @@ -647,6 +647,16 @@ private fun KotlinToCCallBuilder.mapParameter( } } +private fun KotlinStubs.mapFunctionParameterType( + type: IrType, + retained: Boolean, + variadic: Boolean, + location: TypeLocation +): ArgumentPassing = when { + type.isUnit() && !variadic -> IgnoredUnitArgumentPassing + else -> mapType(type, retained = retained, variadic = variadic, location = location) +} + private sealed class TypeLocation(val element: IrElement) { class FunctionArgument(val argument: IrExpression) : TypeLocation(argument) class FunctionCallResult(val call: IrFunctionAccessExpression) : TypeLocation(call) @@ -797,7 +807,7 @@ private fun KotlinStubs.isObjCReferenceType(type: IrType): Boolean { private class CExpression(val expression: String, val type: CType) private interface KotlinToCArgumentPassing { - fun KotlinToCCallBuilder.passValue(expression: IrExpression): CExpression + fun KotlinToCCallBuilder.passValue(expression: IrExpression): CExpression? } private interface ValueReturning { @@ -807,10 +817,12 @@ private interface ValueReturning { fun CCallbackBuilder.returnValue(expression: IrExpression) } -private interface ValuePassing : KotlinToCArgumentPassing, ValueReturning { +private interface ArgumentPassing : KotlinToCArgumentPassing { fun CCallbackBuilder.receiveValue(): IrExpression } +private interface ValuePassing : ArgumentPassing, ValueReturning + private abstract class SimpleValuePassing : ValuePassing { abstract val kotlinBridgeType: IrType abstract val cBridgeType: CType @@ -1410,6 +1422,23 @@ private object VoidReturning : ValueReturning { } } +private object IgnoredUnitArgumentPassing : ArgumentPassing { + override fun KotlinToCCallBuilder.passValue(expression: IrExpression): CExpression? { + // Note: it is not correct to just drop the expression (due to possible side effects), + // so (in lack of other options) evaluate the expression and pass ignored value to the bridge: + val bridgeArgument = irBuilder.irBlock { + +expression + +irInt(0) + } + passThroughBridge(bridgeArgument, irBuilder.context.irBuiltIns.intType, CTypes.int).name + return null + } + + override fun CCallbackBuilder.receiveValue(): IrExpression { + return bridgeBuilder.kotlinIrBuilder.irGetObject(irBuiltIns.unitClass) + } +} + internal fun CType.cast(expression: String): String = "((${this.render("")})$expression)" private fun KotlinStubs.reportUnsupportedType(reason: String, type: IrType, location: TypeLocation): Nothing { diff --git a/backend.native/tests/interop/objc/smoke.h b/backend.native/tests/interop/objc/smoke.h index fa25cf5375a..30f1cfe5f8e 100644 --- a/backend.native/tests/interop/objc/smoke.h +++ b/backend.native/tests/interop/objc/smoke.h @@ -213,3 +213,10 @@ id getPrinterProtocolRaw() { Protocol* getPrinterProtocol() { return @protocol(Printer); } + +@interface TestInitWithCustomSelector : NSObject +-(instancetype)initCustom; +@property BOOL custom; + ++(instancetype _Nonnull)createCustom; +@end; diff --git a/backend.native/tests/interop/objc/smoke.kt b/backend.native/tests/interop/objc/smoke.kt index 7707b0ab50e..4ad90df8b7b 100644 --- a/backend.native/tests/interop/objc/smoke.kt +++ b/backend.native/tests/interop/objc/smoke.kt @@ -25,6 +25,7 @@ fun run() { testOverrideInit() testMultipleInheritanceClash() testClashingWithAny() + testInitWithCustomSelector() assertEquals(2, ForwardDeclaredEnum.TWO.value) @@ -374,6 +375,28 @@ fun testClashingWithAny() { assertTrue(TestClashingWithAny3().equals()) } +fun testInitWithCustomSelector() { + assertFalse(TestInitWithCustomSelector().custom) + assertTrue(TestInitWithCustomSelector(custom = Unit).custom) + + val customSubclass: TestInitWithCustomSelector = TestInitWithCustomSelectorSubclass.createCustom() + assertTrue(customSubclass is TestInitWithCustomSelectorSubclass) + assertTrue(customSubclass.custom) + + // Test side effect: + var ok = false + assertTrue(TestInitWithCustomSelector(run { ok = true }).custom) + assertTrue(ok) +} + +private class TestInitWithCustomSelectorSubclass : TestInitWithCustomSelector { + @OverrideInit constructor(custom: Unit) : super(custom) { + assertSame(Unit, custom) + } + + companion object : TestInitWithCustomSelectorMeta() +} + fun nsArrayOf(vararg elements: Any): NSArray = NSMutableArray().apply { elements.forEach { this.addObject(it as ObjCObject) diff --git a/backend.native/tests/interop/objc/smoke.m b/backend.native/tests/interop/objc/smoke.m index 5485ba105bf..37d110c982d 100644 --- a/backend.native/tests/interop/objc/smoke.m +++ b/backend.native/tests/interop/objc/smoke.m @@ -254,3 +254,25 @@ static CustomRetainMethodsImpl* retainedCustomRetainMethodsImpl; return YES; } @end; + +@implementation TestInitWithCustomSelector + +-(instancetype)initCustom { + if (self = [super init]) { + self.custom = YES; + } + return self; +} + +-(instancetype)init { + if (self = [super init]) { + self.custom = NO; + } + return self; +} + ++(instancetype)createCustom { + return [[self alloc] initCustom]; +} + +@end;