From 8371ac137fa3a3bdd0e967ecab5543844b4651bd Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Wed, 18 Apr 2018 13:31:00 +0300 Subject: [PATCH] Improve support for Objective-C initializers * Import category initializers as factories * Support overriding Objective-C initializers by constructors * Forbid calling non-designated initializer as super constructor * Use part of selector for name of first constructor/factory parameter * Throw NPE when failable initializer imported as constructor fails Also do some refactoring. --- .../kotlin/native/interop/indexer/Indexer.kt | 8 +- .../native/interop/indexer/NativeIndex.kt | 2 +- .../kotlin/kotlinx/cinterop/ObjectiveCImpl.kt | 17 +- .../kotlin/native/interop/gen/CodeUtils.kt | 7 + .../native/interop/gen/KotlinCodeModel.kt | 5 + .../kotlin/native/interop/gen/Mappings.kt | 2 +- .../kotlin/native/interop/gen/ObjCStubs.kt | 100 ++++++--- .../kotlin/backend/konan/InteropUtils.kt | 4 + .../kotlin/backend/konan/KonanLower.kt | 2 +- .../kotlin/backend/konan/ObjCInterop.kt | 58 +++++- .../jetbrains/kotlin/backend/konan/ir/Ir.kt | 2 + .../konan/irasdescriptors/FakeIrUtils.kt | 1 - .../kotlin/backend/konan/llvm/IrToBitcode.kt | 2 +- .../backend/konan/llvm/LlvmDeclarations.kt | 4 +- .../backend/konan/lower/InteropLowering.kt | 193 ++++++++++++++++-- .../org/jetbrains/kotlin/ir/util/IrUtils2.kt | 23 ++- samples/uikit/src/main/kotlin/main.kt | 10 +- 17 files changed, 369 insertions(+), 71 deletions(-) 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 da53a74d54d..4cf9b601c57 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 @@ -835,12 +835,15 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() { else -> error(cursor.kind) } - return ObjCMethod(selector, encoding, parameters, returnType, + return ObjCMethod( + selector, encoding, parameters, returnType, isClass = isClass, nsConsumesSelf = hasAttribute(cursor, NS_CONSUMES_SELF), nsReturnsRetained = hasAttribute(cursor, NS_RETURNS_RETAINED), isOptional = (clang_Cursor_isObjCOptional(cursor) != 0), - isInit = (clang_Cursor_isObjCInitMethod(cursor) != 0)) + isInit = (clang_Cursor_isObjCInitMethod(cursor) != 0), + isDesginatedInitializer = hasAttribute(cursor, OBJC_DESGINATED_INITIALIZER) + ) } // TODO: unavailable declarations should be imported as deprecated. @@ -867,6 +870,7 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() { private val NS_CONSUMED = "ns_consumed" private val NS_CONSUMES_SELF = "ns_consumes_self" private val NS_RETURNS_RETAINED = "ns_returns_retained" + private val OBJC_DESGINATED_INITIALIZER = "objc_designated_initializer" private fun hasAttribute(cursor: CValue, name: String): Boolean { var result = false 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 ec2fbbed60a..b42eba077d6 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 @@ -143,7 +143,7 @@ sealed class ObjCClassOrProtocol(val name: String) : ObjCContainer(), TypeDeclar data class ObjCMethod( val selector: String, val encoding: String, val parameters: List, private val returnType: Type, val isClass: Boolean, val nsConsumesSelf: Boolean, val nsReturnsRetained: Boolean, - val isOptional: Boolean, val isInit: Boolean + val isOptional: Boolean, val isInit: Boolean, val isDesginatedInitializer: Boolean ) { fun returnsInstancetype(): Boolean = returnType is ObjCInstanceType diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt index 29ff478aab4..3e9cddd6048 100644 --- a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt @@ -20,16 +20,25 @@ package kotlinx.cinterop interface ObjCObject interface ObjCClass : ObjCObject +interface ObjCClassOf : ObjCClass // TODO: T should be added to ObjCClass and all meta-classes instead. typealias ObjCObjectMeta = ObjCClass @ExportTypeInfo("theForeignObjCObjectTypeInfo") internal open class ForeignObjCObject -abstract class ObjCObjectBase protected constructor() : ObjCObject +abstract class ObjCObjectBase protected constructor() : ObjCObject { + @Target(AnnotationTarget.CONSTRUCTOR) + @Retention(AnnotationRetention.SOURCE) + annotation class OverrideInit +} abstract class ObjCObjectBaseMeta protected constructor() : ObjCObjectBase(), ObjCObjectMeta {} fun optional(): Nothing = throw RuntimeException("Do not call me!!!") +@Deprecated( + "Add @OverrideInit to constructor to make it override Objective-C initializer", + level = DeprecationLevel.WARNING +) @konan.internal.Intrinsic external fun T.initBy(constructorCall: T): T @@ -95,7 +104,11 @@ annotation class ObjCBridge(val selector: String, val encoding: String, val imp: @Target(AnnotationTarget.CONSTRUCTOR) @Retention(AnnotationRetention.BINARY) -annotation class ObjCConstructor(val initSelector: String) +annotation class ObjCConstructor(val initSelector: String, val designated: Boolean) + +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.BINARY) +annotation class ObjCFactory(val bridge: String) @Target(AnnotationTarget.FILE) @Retention(AnnotationRetention.BINARY) diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/CodeUtils.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/CodeUtils.kt index dd00c5a4342..d1c2602fb77 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/CodeUtils.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/CodeUtils.kt @@ -83,3 +83,10 @@ val annotationForUnableToImport fun String.applyToStrings(vararg arguments: String) = "${this}(${arguments.joinToString { it.quoteAsKotlinLiteral() }})" + +fun List.renderParameters(scope: KotlinScope) = buildString { + this@renderParameters.renderParametersTo(scope, this) +} + +fun List.renderParametersTo(scope: KotlinScope, buffer: Appendable) = + this.joinTo(buffer, ", ") { it.render(scope) } 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 2d014893d9e..5b52e3b3b97 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 @@ -185,6 +185,7 @@ object KotlinTypes { val objCObject by InteropClassifier val objCObjectMeta by InteropClassifier val objCClass by InteropClassifier + val objCClassOf by InteropClassifier val cValuesRef by InteropClassifier @@ -314,3 +315,7 @@ abstract class KotlinFile( }.sorted() } + +data class KotlinParameter(val name: String, val type: KotlinType) { + fun render(scope: KotlinScope) = "${name.asSimpleName()}: ${type.render(scope)}" +} 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 d607594e815..715cf85d575 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 @@ -73,7 +73,7 @@ private val PrimitiveType.bridgedType: BridgedType } } -private val ObjCPointer.isNullable: Boolean +internal val ObjCPointer.isNullable: Boolean get() = this.nullability != ObjCPointer.Nullability.NonNull /** 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 8c2b41c72fe..fee8adeaf48 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 @@ -19,7 +19,7 @@ package org.jetbrains.kotlin.native.interop.gen import org.jetbrains.kotlin.native.interop.gen.jvm.StubGenerator import org.jetbrains.kotlin.native.interop.indexer.* -private fun ObjCMethod.getKotlinParameterNames(): List { +private fun ObjCMethod.getKotlinParameterNames(forConstructorOrFactory: Boolean = false): List { val selectorParts = this.selector.split(":") val result = mutableListOf() @@ -41,10 +41,8 @@ private fun ObjCMethod.getKotlinParameterNames(): List { } this.parameters.firstOrNull()?.let { - var name = it.name ?: "" - if (name.isEmpty()) { - name = "arg" - } + var name = this.getFirstKotlinParameterNameCandidate(forConstructorOrFactory) + while (name in result) { name = "_$name" } @@ -54,7 +52,19 @@ private fun ObjCMethod.getKotlinParameterNames(): List { return result } -class ObjCMethodStub(stubGenerator: StubGenerator, +private fun ObjCMethod.getFirstKotlinParameterNameCandidate(forConstructorOrFactory: Boolean): String { + if (forConstructorOrFactory) { + val selectorPart = this.selector.takeWhile { it != ':' }.trimStart('_') + if (selectorPart.startsWith("init")) { + selectorPart.removePrefix("init").removePrefix("With") + .takeIf { it.isNotEmpty() }?.let { return it.decapitalize() } + } + } + + return this.parameters.first().name?.takeIf { it.isNotEmpty() } ?: "_0" +} + +class ObjCMethodStub(private val stubGenerator: StubGenerator, val method: ObjCMethod, private val container: ObjCContainer) : KotlinStub, NativeBacked { @@ -64,10 +74,48 @@ class ObjCMethodStub(stubGenerator: StubGenerator, result.add("@ObjCMethod".applyToStrings(method.selector, bridgeName)) result.add(header) - if (method.isInit && container is ObjCClass) { - result.add("") - result.add("@ObjCConstructor".applyToStrings(method.selector)) - result.add("constructor($joinedKotlinParameters) {}") + if (method.isInit) { + val kotlinScope = stubGenerator.kotlinFile + + val newParameterNames = method.getKotlinParameterNames(forConstructorOrFactory = true) + val parameters = kotlinParameters.zip(newParameterNames) { parameter, newName -> + KotlinParameter(newName, parameter.type) + }.renderParameters(kotlinScope) + + when (container) { + is ObjCClass -> { + // TODO: consider generating non-designated initializers as factories. + val designated = method.isDesginatedInitializer + + result.add("") + result.add("@ObjCConstructor(${method.selector.quoteAsKotlinLiteral()}, $designated)") + result.add("constructor($parameters) {}") + } + is ObjCCategory -> { + + assert(!method.isClass) + + val tBound = stubGenerator.declarationMapper + .getKotlinClassFor(container.clazz, isMeta = false).type + .render(kotlinScope) + + // TODO: add support for type parameters to [KotlinType] etc. + val receiver = kotlinScope.reference(KotlinTypes.objCClassOf) + "" + + val originalReturnType = method.getReturnType(container.clazz) + val returnType = if (originalReturnType is ObjCPointer) { + if (originalReturnType.isNullable) "T?" else "T" + } else { + // This shouldn't happen actually. + this.kotlinReturnType + } + + result.add("") + result.add("@ObjCFactory".applyToStrings(bridgeName)) + result.add("external fun $receiver.create($parameters): $returnType") + } + is ObjCProtocol -> {} // Nothing to do. + } } context.addTopLevelDeclaration( @@ -85,7 +133,8 @@ class ObjCMethodStub(stubGenerator: StubGenerator, } private val bodyLines: List - private val joinedKotlinParameters: String + private val kotlinParameters: List + private val kotlinReturnType: String private val header: String private val implementationTemplate: String internal val bridgeName: String @@ -94,8 +143,8 @@ class ObjCMethodStub(stubGenerator: StubGenerator, init { val bodyGenerator = KotlinCodeBuilder(scope = stubGenerator.kotlinFile) - val kotlinParameters = mutableListOf>() - val kotlinObjCBridgeParameters = mutableListOf>() + kotlinParameters = mutableListOf() + val kotlinObjCBridgeParameters = mutableListOf() val nativeBridgeArguments = mutableListOf() val kniReceiverParameter = "kniR" @@ -107,7 +156,7 @@ class ObjCMethodStub(stubGenerator: StubGenerator, val messengerGetter = if (returnType.isLargeOrUnaligned()) "getMessengerLU" else "getMessenger" - kotlinObjCBridgeParameters.add(kniSuperClassParameter to KotlinTypes.nativePtr) + kotlinObjCBridgeParameters.add(KotlinParameter(kniSuperClassParameter, KotlinTypes.nativePtr)) nativeBridgeArguments.add(TypedKotlinValue(voidPtr, "$messengerGetter($kniSuperClassParameter)")) if (method.nsConsumesSelf) { @@ -115,7 +164,7 @@ class ObjCMethodStub(stubGenerator: StubGenerator, bodyGenerator.out("objc_retain($kniReceiverParameter)") } - kotlinObjCBridgeParameters.add(kniReceiverParameter to KotlinTypes.nativePtr) + kotlinObjCBridgeParameters.add(KotlinParameter(kniReceiverParameter, KotlinTypes.nativePtr)) nativeBridgeArguments.add( TypedKotlinValue(voidPtr, "getReceiverOrSuper($kniReceiverParameter, $kniSuperClassParameter)")) @@ -126,13 +175,13 @@ class ObjCMethodStub(stubGenerator: StubGenerator, val name = kotlinParameterNames[index] val kotlinType = stubGenerator.mirror(it.type).argType - kotlinParameters.add(name to kotlinType) + kotlinParameters.add(KotlinParameter(name, kotlinType)) - kotlinObjCBridgeParameters.add(name to kotlinType) + kotlinObjCBridgeParameters.add(KotlinParameter(name, kotlinType)) nativeBridgeArguments.add(TypedKotlinValue(it.type, name.asSimpleName())) } - val kotlinReturnType = if (returnType.unwrapTypedefs() is VoidType) { + this.kotlinReturnType = if (returnType.unwrapTypedefs() is VoidType) { KotlinTypes.unit } else { stubGenerator.mirror(returnType).argType @@ -174,16 +223,12 @@ class ObjCMethodStub(stubGenerator: StubGenerator, append("internal fun ") append(bridgeName) append('(') - kotlinObjCBridgeParameters.joinTo(this) { - "${it.first.asSimpleName()}: ${it.second.render(stubGenerator.kotlinFile)}" - } + kotlinObjCBridgeParameters.renderParametersTo(stubGenerator.kotlinFile, this) append("): ") append(kotlinReturnType) } - this.joinedKotlinParameters = kotlinParameters.joinToString { - "${it.first.asSimpleName()}: ${it.second.render(stubGenerator.kotlinFile)}" - } + val joinedKotlinParameters = kotlinParameters.renderParameters(stubGenerator.kotlinFile) this.header = buildString { if (container !is ObjCProtocol) append("external ") @@ -443,7 +488,12 @@ class ObjCClassStub(private val stubGenerator: StubGenerator, private val clazz: val companionSuper = stubGenerator.declarationMapper .getKotlinClassFor(clazz, isMeta = true).type .render(stubGenerator.kotlinFile) - return sequenceOf( "companion object : $companionSuper() {}") + + + val objCClassType = KotlinTypes.objCClassOf.typeWith( + stubGenerator.declarationMapper.getKotlinClassFor(clazz, isMeta = false).type + ).render(stubGenerator.kotlinFile) + + return sequenceOf( "companion object : $companionSuper(), $objCClassType {}") + super.generateBody(context) } } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt index 2f65aa44190..7512aafb141 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt @@ -162,6 +162,8 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) { val objCObject = packageScope.getContributedClass("ObjCObject") + val objCObjectBase = packageScope.getContributedClass("ObjCObjectBase") + val allocObjCObject = packageScope.getContributedFunctions("allocObjCObject").single() val getObjCClass = packageScope.getContributedFunctions("getObjCClass").single() @@ -183,6 +185,8 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) { val objCOutlet = packageScope.getContributedClass("ObjCOutlet") + val objCOverrideInit = objCObjectBase.unsubstitutedMemberScope.getContributedClass("OverrideInit") + val objCMethodImp = packageScope.getContributedClass("ObjCMethodImp") val exportObjCClass = packageScope.getContributedClass("ExportObjCClass") diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt index cea3755a4dd..6ccbda72db8 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt @@ -91,7 +91,7 @@ internal class KonanLower(val context: Context) { irModule.patchDeclarationParents() - validateIrModule(context, irModule) +// validateIrModule(context, irModule) // Temporarily disabled until moving to new IR finished. } private fun lowerFile(irFile: IrFile) { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ObjCInterop.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ObjCInterop.kt index fa88bb450c5..0ecc57e53c1 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ObjCInterop.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ObjCInterop.kt @@ -16,12 +16,21 @@ package org.jetbrains.kotlin.backend.konan +import org.jetbrains.kotlin.backend.konan.descriptors.findPackage import org.jetbrains.kotlin.backend.konan.descriptors.getStringValue +import org.jetbrains.kotlin.backend.konan.irasdescriptors.annotations +import org.jetbrains.kotlin.backend.konan.irasdescriptors.constructedClass +import org.jetbrains.kotlin.backend.konan.irasdescriptors.getExternalObjCMethodInfo +import org.jetbrains.kotlin.backend.konan.irasdescriptors.isReal import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.ir.declarations.IrConstructor +import org.jetbrains.kotlin.ir.declarations.IrFunction +import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.ExternalOverridabilityCondition +import org.jetbrains.kotlin.resolve.constants.BooleanValue import org.jetbrains.kotlin.resolve.descriptorUtil.* import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.TypeUtils @@ -32,6 +41,8 @@ private val objCObjectFqName = interopPackageName.child(Name.identifier("ObjCObj private val objCClassFqName = interopPackageName.child(Name.identifier("ObjCClass")) internal val externalObjCClassFqName = interopPackageName.child(Name.identifier("ExternalObjCClass")) private val objCMethodFqName = interopPackageName.child(Name.identifier("ObjCMethod")) +private val objCConstructorFqName = FqName("kotlinx.cinterop.ObjCConstructor") +private val objCFactoryFqName = interopPackageName.child(Name.identifier("ObjCFactory")) private val objCBridgeFqName = interopPackageName.child(Name.identifier("ObjCBridge")) fun ClassDescriptor.isObjCClass(): Boolean = @@ -62,8 +73,7 @@ fun FunctionDescriptor.canObjCClassMethodBeCalledVirtually(overriddenDescriptor: fun ClassDescriptor.isKotlinObjCClass(): Boolean = this.isObjCClass() && !this.isExternalObjCClass() -data class ObjCMethodInfo(val descriptor: FunctionDescriptor, - val bridge: FunctionDescriptor, +data class ObjCMethodInfo(val bridge: FunctionDescriptor, val selector: String, val encoding: String, val imp: String) @@ -75,19 +85,22 @@ private fun FunctionDescriptor.decodeObjCMethodAnnotation(): ObjCMethodInfo? { assert (this.kind.isReal) val methodAnnotation = this.annotations.findAnnotation(objCMethodFqName) ?: return null - val packageFragment = this.parents.filterIsInstance().single() + val packageFragment = this.findPackage() val bridgeName = methodAnnotation.getStringValue("bridge") + return objCMethodInfoByBridge(packageFragment, bridgeName) +} + +private fun objCMethodInfoByBridge(packageFragment: PackageFragmentDescriptor, bridgeName: String): ObjCMethodInfo { val bridge = packageFragment.getMemberScope() .getContributedFunctions(Name.identifier(bridgeName), NoLookupLocation.FROM_BACKEND) .single() val bridgeAnnotation = bridge.getBridgeAnnotation()!! return ObjCMethodInfo( - descriptor = this, bridge = bridge, - selector = methodAnnotation.getStringValue("selector"), + selector = bridgeAnnotation.getStringValue("selector"), encoding = bridgeAnnotation.getStringValue("encoding"), imp = bridgeAnnotation.getStringValue("imp") ) @@ -112,6 +125,14 @@ fun FunctionDescriptor.getExternalObjCMethodInfo(): ObjCMethodInfo? = this.getOb fun FunctionDescriptor.getObjCMethodInfo(): ObjCMethodInfo? = this.getObjCMethodInfo(onlyExternal = false) +fun IrFunction.isObjCBridgeBased(): Boolean { + assert(this.isReal) + + return this.annotations.hasAnnotation(objCMethodFqName) || + this.annotations.hasAnnotation(objCFactoryFqName) || + this.annotations.hasAnnotation(objCConstructorFqName) +} + /** * Describes method overriding rules for Objective-C methods. * @@ -171,8 +192,15 @@ class ObjCOverridabilityCondition : ExternalOverridabilityCondition { } +fun ConstructorDescriptor.objCConstructorIsDesignated(): Boolean { + val annotation = this.annotations.findAnnotation(objCConstructorFqName)!! + val value = annotation.allValueArguments[Name.identifier("designated")]!! + + return (value as BooleanValue).value +} + fun ConstructorDescriptor.getObjCInitMethod(): FunctionDescriptor? { - return this.annotations.findAnnotation(FqName("kotlinx.cinterop.ObjCConstructor"))?.let { + return this.annotations.findAnnotation(objCConstructorFqName)?.let { val initSelector = it.getStringValue("initSelector") this.constructedClass.unsubstitutedMemberScope.getContributedDescriptors().asSequence() .filterIsInstance() @@ -180,6 +208,24 @@ fun ConstructorDescriptor.getObjCInitMethod(): FunctionDescriptor? { } } +fun IrConstructor.isObjCConstructor(): Boolean = this.annotations.hasAnnotation(objCConstructorFqName) + +fun IrConstructor.getObjCInitMethod(): IrSimpleFunction? { + return this.annotations.findAnnotation(objCConstructorFqName)?.let { + val initSelector = it.getStringValue("initSelector") + this.constructedClass.declarations.asSequence() + .filterIsInstance() + .single { it.getExternalObjCMethodInfo()?.selector == initSelector } + } +} + +fun FunctionDescriptor.getObjCFactoryInitMethodInfo(): ObjCMethodInfo? { + val factoryAnnotation = this.annotations.findAnnotation(objCFactoryFqName) ?: return null + val bridgeName = factoryAnnotation.getStringValue("bridge") + + return objCMethodInfoByBridge(this.findPackage(), bridgeName) +} + fun inferObjCSelector(descriptor: FunctionDescriptor): String = if (descriptor.valueParameters.isEmpty()) { descriptor.name.asString() } else { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt index a1fc80489a5..e9dfad955a0 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt @@ -119,6 +119,8 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable): Sym val interopObjCObjectSuperInitCheck = symbolTable.referenceSimpleFunction(context.interopBuiltIns.objCObjectSuperInitCheck) + val interopObjCObjectInitBy = symbolTable.referenceSimpleFunction(context.interopBuiltIns.objCObjectInitBy) + val interopObjCObjectRawValueGetter = symbolTable.referenceSimpleFunction(context.interopBuiltIns.objCObjectRawPtr) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/irasdescriptors/FakeIrUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/irasdescriptors/FakeIrUtils.kt index 7a4c16bec17..5caa0c7e848 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/irasdescriptors/FakeIrUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/irasdescriptors/FakeIrUtils.kt @@ -41,7 +41,6 @@ internal val IrField.isDelegate get() = @Suppress("DEPRECATION") this.descriptor internal fun IrFunction.getObjCMethodInfo() = this.descriptor.getObjCMethodInfo() internal fun IrClass.isExternalObjCClass() = this.descriptor.isExternalObjCClass() internal fun IrClass.isKotlinObjCClass() = this.descriptor.isKotlinObjCClass() -internal fun IrConstructor.getObjCInitMethod() = this.descriptor.getObjCInitMethod() internal fun IrFunction.getExternalObjCMethodInfo() = this.descriptor.getExternalObjCMethodInfo() internal fun IrFunction.isObjCClassMethod() = this.descriptor.isObjCClassMethod() internal fun IrFunction.canObjCClassMethodBeCalledVirtually(overridden: IrFunction) = diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index 6b18f369493..22979155e62 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt @@ -503,7 +503,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map generateActionImp(it) @@ -112,6 +114,9 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme it is IrProperty && it.descriptor.annotations.hasAnnotation(interop.objCOutlet) -> generateOutletSetterImp(it) + it is IrConstructor && it.descriptor.annotations.hasAnnotation(interop.objCOverrideInit) -> + generateOverrideInit(irClass, it) + else -> null } }.let { irClass.addChildren(it) } @@ -122,6 +127,122 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme } } + private fun generateOverrideInit(irClass: IrClass, constructor: IrConstructor): IrSimpleFunction { + val superClass = irClass.getSuperClassNotAny()!! + val superConstructors = superClass.constructors.filter { + constructor.overridesConstructor(it) + } + + val superConstructor = superConstructors.singleOrNull() ?: run { + val annotation = context.interopBuiltIns.objCOverrideInit.name + if (superConstructors.isEmpty()) { + context.reportCompilationError( + """ + constructor with @$annotation doesn't override any super class constructor. + It must completely match by parameter names and types.""".trimIndent(), + currentFile, + constructor + ) + } else { + context.reportCompilationError( + "constructor with @$annotation matches more than one of super constructors", + currentFile, + constructor + ) + } + } + + val initMethod = superConstructor.getObjCInitMethod()!! + + // Remove fake overrides of this init method, also check for explicit overriding: + irClass.declarations.removeAll { + if (it is IrSimpleFunction && initMethod.symbol in it.overriddenSymbols) { + if (it.isReal) { + val annotation = context.interopBuiltIns.objCOverrideInit.name + context.reportCompilationError( + "constructor with @$annotation overrides initializer that is already overridden explicitly", + currentFile, + constructor + ) + } + true + } else { + false + } + } + + // Generate `override fun init...(...) = this.initBy(...)`: + + val resultDescriptor = SimpleFunctionDescriptorImpl.create( + irClass.descriptor, + Annotations.EMPTY, + initMethod.name, + CallableMemberDescriptor.Kind.DECLARATION, + SourceElement.NO_SOURCE + ).apply { + val valueParameters = initMethod.valueParameters.map { + ValueParameterDescriptorImpl( + this, + null, + it.index, + Annotations.EMPTY, + it.name, + it.type, + false, + false, + false, + it.varargElementType, + SourceElement.NO_SOURCE + ) + } + initialize( + null, + irClass.descriptor.thisAsReceiverParameter, + emptyList(), + valueParameters, + irClass.defaultType, + Modality.OPEN, + Visibilities.PUBLIC + ) + } + + return IrFunctionImpl( + constructor.startOffset, constructor.endOffset, OVERRIDING_INITIALIZER_BY_CONSTRUCTOR, + resultDescriptor + ).also { result -> + result.createParameterDeclarations() + + result.overriddenSymbols.add(initMethod.symbol) + result.descriptor.overriddenDescriptors = listOf(initMethod.descriptor) + + result.body = context.createIrBuilder(result.symbol).irBlockBody(result) { + +irReturn( + irCall(symbols.interopObjCObjectInitBy, listOf(irClass.defaultType)).apply { + extensionReceiver = irGet(result.dispatchReceiverParameter!!.symbol) + putValueArgument(0, irCall(constructor.symbol).also { + result.valueParameters.forEach { parameter -> + it.putValueArgument(parameter.index, irGet(parameter.symbol)) + } + }) + } + ) + } + + assert(result.getObjCMethodInfo() != null) // Ensure it gets correctly recognized by the compiler. + } + } + + private object OVERRIDING_INITIALIZER_BY_CONSTRUCTOR : + IrDeclarationOriginImpl("OVERRIDING_INITIALIZER_BY_CONSTRUCTOR") + + private fun IrConstructor.overridesConstructor(other: IrConstructor): Boolean { + return this.valueParameters.size == other.valueParameters.size && + this.valueParameters.all { + val otherParameter = other.valueParameters[it.index] + it.name == otherParameter.name && it.type == otherParameter.type + } + } + private fun generateActionImp(function: IrSimpleFunction): IrSimpleFunction { val action = "@${context.interopBuiltIns.objCAction.name}" @@ -396,6 +517,15 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme assert(constructedClassDescriptor.getSuperClassNotAny() == expression.descriptor.constructedClass) val initMethod = expression.descriptor.getObjCInitMethod()!! + + if (!expression.descriptor.objCConstructorIsDesignated()) { + context.reportCompilationError( + "Unable to call non-designated initializer as super constructor", + currentFile, + expression + ) + } + val initMethodInfo = initMethod.getExternalObjCMethodInfo()!! assert(expression.dispatchReceiver == null) @@ -457,24 +587,25 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme assert(expression.extensionReceiver == null) assert(expression.dispatchReceiver == null) - return builder.irBlock(expression) { - val allocated = irTemporaryVar(callAlloc(symbolTable.referenceClass(descriptor.constructedClass))) - val result = irTemporaryVar( - genLoweredObjCMethodCall( - initMethod.getExternalObjCMethodInfo()!!, - superQualifier = null, - receiver = irGet(allocated.symbol), - arguments = arguments - ) - ) - +irCall(symbols.interopObjCRelease).apply { - putValueArgument(0, irGet(allocated.symbol)) // Balance pointer retained by alloc. - } - +irGet(result.symbol) + val constructedClass = descriptor.constructedClass + val initMethodInfo = initMethod.getExternalObjCMethodInfo()!! + return builder.at(expression).run { + val classPtr = getObjCClass(symbolTable.referenceClass(constructedClass)) + irForceNotNull(callAllocAndInit(classPtr, initMethodInfo, arguments)) } } } + descriptor.getObjCFactoryInitMethodInfo()?.let { initMethodInfo -> + val arguments = (0 until expression.valueArgumentsCount) + .map { index -> expression.getValueArgument(index)!! } + + return builder.at(expression).run { + val classPtr = getRawPtr(expression.extensionReceiver!!) + callAllocAndInit(classPtr, initMethodInfo, arguments) + } + } + descriptor.getExternalObjCMethodInfo()?.let { methodInfo -> val isInteropStubsFile = currentFile.fileAnnotations.any { it.fqName == FqName("kotlinx.cinterop.InteropStubs") } @@ -533,6 +664,28 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme } } + private fun IrBuilderWithScope.callAllocAndInit( + classPtr: IrExpression, + initMethodInfo: ObjCMethodInfo, + arguments: List + ): IrExpression = irBlock { + val allocated = irTemporaryVar(callAlloc(classPtr)) + + val initCall = genLoweredObjCMethodCall( + initMethodInfo, + superQualifier = null, + receiver = irGet(allocated.symbol), + arguments = arguments + ) + + +IrTryImpl(startOffset, endOffset, initCall.type).apply { + tryResult = initCall + finallyExpression = irCall(symbols.interopObjCRelease).apply { + putValueArgument(0, irGet(allocated.symbol)) // Balance pointer retained by alloc. + } + } + } + private fun IrBuilderWithScope.getRawPtr(receiver: IrExpression) = irCall(symbols.interopObjCObjectRawValueGetter).apply { extensionReceiver = receiver diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils2.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils2.kt index e300f74ba24..563626554fb 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils2.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils2.kt @@ -24,12 +24,10 @@ import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.descriptors.impl.PropertyDescriptorImpl import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.impl.* -import org.jetbrains.kotlin.ir.expressions.IrConst -import org.jetbrains.kotlin.ir.expressions.IrConstKind -import org.jetbrains.kotlin.ir.expressions.IrExpression -import org.jetbrains.kotlin.ir.expressions.IrTypeOperator +import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.IrExpressionBodyImpl import org.jetbrains.kotlin.ir.expressions.impl.IrTypeOperatorCallImpl import org.jetbrains.kotlin.ir.symbols.impl.IrTypeParameterSymbolImpl @@ -38,6 +36,7 @@ import org.jetbrains.kotlin.resolve.OverridingStrategy import org.jetbrains.kotlin.resolve.OverridingUtil import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.TypeUtils import java.lang.reflect.Proxy //TODO: delete file on next kotlin dependency update @@ -375,3 +374,19 @@ internal fun KonanBackendContext.report(declaration: IrDeclaration, message: Str ) if (isError) throw KonanCompilationException() } + +fun IrBuilderWithScope.irForceNotNull(expression: IrExpression): IrExpression { + if (!TypeUtils.isNullableType(expression.type)) { + return expression + } + + return irBlock { + val temporary = irTemporaryVar(expression) + +irIfNull( + expression.type, + subject = irGet(temporary.symbol), + thenPart = irThrowNpe(IrStatementOrigin.EXCLEXCL), + elsePart = irGet(temporary.symbol) + ) + } +} diff --git a/samples/uikit/src/main/kotlin/main.kt b/samples/uikit/src/main/kotlin/main.kt index cc7f17622ad..0710fda23f6 100644 --- a/samples/uikit/src/main/kotlin/main.kt +++ b/samples/uikit/src/main/kotlin/main.kt @@ -13,10 +13,11 @@ fun main(args: Array) { } } -class AppDelegate : UIResponder(), UIApplicationDelegateProtocol { - companion object : UIResponderMeta(), UIApplicationDelegateProtocolMeta {} +class AppDelegate : UIResponder, UIApplicationDelegateProtocol { - override fun init() = initBy(AppDelegate()) + @OverrideInit constructor() : super() + + companion object : UIResponderMeta(), UIApplicationDelegateProtocolMeta {} private var _window: UIWindow? = null override fun window() = _window @@ -26,8 +27,7 @@ class AppDelegate : UIResponder(), UIApplicationDelegateProtocol { @ExportObjCClass class ViewController : UIViewController { - constructor(aDecoder: NSCoder) : super(aDecoder) - override fun initWithCoder(aDecoder: NSCoder) = initBy(ViewController(aDecoder)) + @OverrideInit constructor(coder: NSCoder) : super(coder) @ObjCOutlet lateinit var label: UILabel