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.
This commit is contained in:
Svyatoslav Scherbina
2018-04-18 13:31:00 +03:00
committed by SvyatoslavScherbina
parent 688b3f26f2
commit 8371ac137f
17 changed files with 369 additions and 71 deletions
@@ -835,12 +835,15 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
else -> error(cursor.kind) else -> error(cursor.kind)
} }
return ObjCMethod(selector, encoding, parameters, returnType, return ObjCMethod(
selector, encoding, parameters, returnType,
isClass = isClass, isClass = isClass,
nsConsumesSelf = hasAttribute(cursor, NS_CONSUMES_SELF), nsConsumesSelf = hasAttribute(cursor, NS_CONSUMES_SELF),
nsReturnsRetained = hasAttribute(cursor, NS_RETURNS_RETAINED), nsReturnsRetained = hasAttribute(cursor, NS_RETURNS_RETAINED),
isOptional = (clang_Cursor_isObjCOptional(cursor) != 0), 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. // 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_CONSUMED = "ns_consumed"
private val NS_CONSUMES_SELF = "ns_consumes_self" private val NS_CONSUMES_SELF = "ns_consumes_self"
private val NS_RETURNS_RETAINED = "ns_returns_retained" private val NS_RETURNS_RETAINED = "ns_returns_retained"
private val OBJC_DESGINATED_INITIALIZER = "objc_designated_initializer"
private fun hasAttribute(cursor: CValue<CXCursor>, name: String): Boolean { private fun hasAttribute(cursor: CValue<CXCursor>, name: String): Boolean {
var result = false var result = false
@@ -143,7 +143,7 @@ sealed class ObjCClassOrProtocol(val name: String) : ObjCContainer(), TypeDeclar
data class ObjCMethod( data class ObjCMethod(
val selector: String, val encoding: String, val parameters: List<Parameter>, private val returnType: Type, val selector: String, val encoding: String, val parameters: List<Parameter>, private val returnType: Type,
val isClass: Boolean, val nsConsumesSelf: Boolean, val nsReturnsRetained: Boolean, 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 fun returnsInstancetype(): Boolean = returnType is ObjCInstanceType
@@ -20,16 +20,25 @@ package kotlinx.cinterop
interface ObjCObject interface ObjCObject
interface ObjCClass : ObjCObject interface ObjCClass : ObjCObject
interface ObjCClassOf<T : ObjCObject> : ObjCClass // TODO: T should be added to ObjCClass and all meta-classes instead.
typealias ObjCObjectMeta = ObjCClass typealias ObjCObjectMeta = ObjCClass
@ExportTypeInfo("theForeignObjCObjectTypeInfo") @ExportTypeInfo("theForeignObjCObjectTypeInfo")
internal open class ForeignObjCObject 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 {} abstract class ObjCObjectBaseMeta protected constructor() : ObjCObjectBase(), ObjCObjectMeta {}
fun optional(): Nothing = throw RuntimeException("Do not call me!!!") 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 @konan.internal.Intrinsic
external fun <T : ObjCObjectBase> T.initBy(constructorCall: T): T external fun <T : ObjCObjectBase> T.initBy(constructorCall: T): T
@@ -95,7 +104,11 @@ annotation class ObjCBridge(val selector: String, val encoding: String, val imp:
@Target(AnnotationTarget.CONSTRUCTOR) @Target(AnnotationTarget.CONSTRUCTOR)
@Retention(AnnotationRetention.BINARY) @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) @Target(AnnotationTarget.FILE)
@Retention(AnnotationRetention.BINARY) @Retention(AnnotationRetention.BINARY)
@@ -83,3 +83,10 @@ val annotationForUnableToImport
fun String.applyToStrings(vararg arguments: String) = fun String.applyToStrings(vararg arguments: String) =
"${this}(${arguments.joinToString { it.quoteAsKotlinLiteral() }})" "${this}(${arguments.joinToString { it.quoteAsKotlinLiteral() }})"
fun List<KotlinParameter>.renderParameters(scope: KotlinScope) = buildString {
this@renderParameters.renderParametersTo(scope, this)
}
fun List<KotlinParameter>.renderParametersTo(scope: KotlinScope, buffer: Appendable) =
this.joinTo(buffer, ", ") { it.render(scope) }
@@ -185,6 +185,7 @@ object KotlinTypes {
val objCObject by InteropClassifier val objCObject by InteropClassifier
val objCObjectMeta by InteropClassifier val objCObjectMeta by InteropClassifier
val objCClass by InteropClassifier val objCClass by InteropClassifier
val objCClassOf by InteropClassifier
val cValuesRef by InteropClassifier val cValuesRef by InteropClassifier
@@ -314,3 +315,7 @@ abstract class KotlinFile(
}.sorted() }.sorted()
} }
data class KotlinParameter(val name: String, val type: KotlinType) {
fun render(scope: KotlinScope) = "${name.asSimpleName()}: ${type.render(scope)}"
}
@@ -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 get() = this.nullability != ObjCPointer.Nullability.NonNull
/** /**
@@ -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.gen.jvm.StubGenerator
import org.jetbrains.kotlin.native.interop.indexer.* import org.jetbrains.kotlin.native.interop.indexer.*
private fun ObjCMethod.getKotlinParameterNames(): List<String> { private fun ObjCMethod.getKotlinParameterNames(forConstructorOrFactory: Boolean = false): List<String> {
val selectorParts = this.selector.split(":") val selectorParts = this.selector.split(":")
val result = mutableListOf<String>() val result = mutableListOf<String>()
@@ -41,10 +41,8 @@ private fun ObjCMethod.getKotlinParameterNames(): List<String> {
} }
this.parameters.firstOrNull()?.let { this.parameters.firstOrNull()?.let {
var name = it.name ?: "" var name = this.getFirstKotlinParameterNameCandidate(forConstructorOrFactory)
if (name.isEmpty()) {
name = "arg"
}
while (name in result) { while (name in result) {
name = "_$name" name = "_$name"
} }
@@ -54,7 +52,19 @@ private fun ObjCMethod.getKotlinParameterNames(): List<String> {
return result 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, val method: ObjCMethod,
private val container: ObjCContainer) : KotlinStub, NativeBacked { private val container: ObjCContainer) : KotlinStub, NativeBacked {
@@ -64,10 +74,48 @@ class ObjCMethodStub(stubGenerator: StubGenerator,
result.add("@ObjCMethod".applyToStrings(method.selector, bridgeName)) result.add("@ObjCMethod".applyToStrings(method.selector, bridgeName))
result.add(header) result.add(header)
if (method.isInit && container is ObjCClass) { if (method.isInit) {
result.add("") val kotlinScope = stubGenerator.kotlinFile
result.add("@ObjCConstructor".applyToStrings(method.selector))
result.add("constructor($joinedKotlinParameters) {}") 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) + "<T>"
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 <T : $tBound> $receiver.create($parameters): $returnType")
}
is ObjCProtocol -> {} // Nothing to do.
}
} }
context.addTopLevelDeclaration( context.addTopLevelDeclaration(
@@ -85,7 +133,8 @@ class ObjCMethodStub(stubGenerator: StubGenerator,
} }
private val bodyLines: List<String> private val bodyLines: List<String>
private val joinedKotlinParameters: String private val kotlinParameters: List<KotlinParameter>
private val kotlinReturnType: String
private val header: String private val header: String
private val implementationTemplate: String private val implementationTemplate: String
internal val bridgeName: String internal val bridgeName: String
@@ -94,8 +143,8 @@ class ObjCMethodStub(stubGenerator: StubGenerator,
init { init {
val bodyGenerator = KotlinCodeBuilder(scope = stubGenerator.kotlinFile) val bodyGenerator = KotlinCodeBuilder(scope = stubGenerator.kotlinFile)
val kotlinParameters = mutableListOf<Pair<String, KotlinType>>() kotlinParameters = mutableListOf()
val kotlinObjCBridgeParameters = mutableListOf<Pair<String, KotlinType>>() val kotlinObjCBridgeParameters = mutableListOf<KotlinParameter>()
val nativeBridgeArguments = mutableListOf<TypedKotlinValue>() val nativeBridgeArguments = mutableListOf<TypedKotlinValue>()
val kniReceiverParameter = "kniR" val kniReceiverParameter = "kniR"
@@ -107,7 +156,7 @@ class ObjCMethodStub(stubGenerator: StubGenerator,
val messengerGetter = if (returnType.isLargeOrUnaligned()) "getMessengerLU" else "getMessenger" 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)")) nativeBridgeArguments.add(TypedKotlinValue(voidPtr, "$messengerGetter($kniSuperClassParameter)"))
if (method.nsConsumesSelf) { if (method.nsConsumesSelf) {
@@ -115,7 +164,7 @@ class ObjCMethodStub(stubGenerator: StubGenerator,
bodyGenerator.out("objc_retain($kniReceiverParameter)") bodyGenerator.out("objc_retain($kniReceiverParameter)")
} }
kotlinObjCBridgeParameters.add(kniReceiverParameter to KotlinTypes.nativePtr) kotlinObjCBridgeParameters.add(KotlinParameter(kniReceiverParameter, KotlinTypes.nativePtr))
nativeBridgeArguments.add( nativeBridgeArguments.add(
TypedKotlinValue(voidPtr, TypedKotlinValue(voidPtr,
"getReceiverOrSuper($kniReceiverParameter, $kniSuperClassParameter)")) "getReceiverOrSuper($kniReceiverParameter, $kniSuperClassParameter)"))
@@ -126,13 +175,13 @@ class ObjCMethodStub(stubGenerator: StubGenerator,
val name = kotlinParameterNames[index] val name = kotlinParameterNames[index]
val kotlinType = stubGenerator.mirror(it.type).argType 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())) nativeBridgeArguments.add(TypedKotlinValue(it.type, name.asSimpleName()))
} }
val kotlinReturnType = if (returnType.unwrapTypedefs() is VoidType) { this.kotlinReturnType = if (returnType.unwrapTypedefs() is VoidType) {
KotlinTypes.unit KotlinTypes.unit
} else { } else {
stubGenerator.mirror(returnType).argType stubGenerator.mirror(returnType).argType
@@ -174,16 +223,12 @@ class ObjCMethodStub(stubGenerator: StubGenerator,
append("internal fun ") append("internal fun ")
append(bridgeName) append(bridgeName)
append('(') append('(')
kotlinObjCBridgeParameters.joinTo(this) { kotlinObjCBridgeParameters.renderParametersTo(stubGenerator.kotlinFile, this)
"${it.first.asSimpleName()}: ${it.second.render(stubGenerator.kotlinFile)}"
}
append("): ") append("): ")
append(kotlinReturnType) append(kotlinReturnType)
} }
this.joinedKotlinParameters = kotlinParameters.joinToString { val joinedKotlinParameters = kotlinParameters.renderParameters(stubGenerator.kotlinFile)
"${it.first.asSimpleName()}: ${it.second.render(stubGenerator.kotlinFile)}"
}
this.header = buildString { this.header = buildString {
if (container !is ObjCProtocol) append("external ") if (container !is ObjCProtocol) append("external ")
@@ -443,7 +488,12 @@ class ObjCClassStub(private val stubGenerator: StubGenerator, private val clazz:
val companionSuper = stubGenerator.declarationMapper val companionSuper = stubGenerator.declarationMapper
.getKotlinClassFor(clazz, isMeta = true).type .getKotlinClassFor(clazz, isMeta = true).type
.render(stubGenerator.kotlinFile) .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) super.generateBody(context)
} }
} }
@@ -162,6 +162,8 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) {
val objCObject = packageScope.getContributedClass("ObjCObject") val objCObject = packageScope.getContributedClass("ObjCObject")
val objCObjectBase = packageScope.getContributedClass("ObjCObjectBase")
val allocObjCObject = packageScope.getContributedFunctions("allocObjCObject").single() val allocObjCObject = packageScope.getContributedFunctions("allocObjCObject").single()
val getObjCClass = packageScope.getContributedFunctions("getObjCClass").single() val getObjCClass = packageScope.getContributedFunctions("getObjCClass").single()
@@ -183,6 +185,8 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) {
val objCOutlet = packageScope.getContributedClass("ObjCOutlet") val objCOutlet = packageScope.getContributedClass("ObjCOutlet")
val objCOverrideInit = objCObjectBase.unsubstitutedMemberScope.getContributedClass("OverrideInit")
val objCMethodImp = packageScope.getContributedClass("ObjCMethodImp") val objCMethodImp = packageScope.getContributedClass("ObjCMethodImp")
val exportObjCClass = packageScope.getContributedClass("ExportObjCClass") val exportObjCClass = packageScope.getContributedClass("ExportObjCClass")
@@ -91,7 +91,7 @@ internal class KonanLower(val context: Context) {
irModule.patchDeclarationParents() irModule.patchDeclarationParents()
validateIrModule(context, irModule) // validateIrModule(context, irModule) // Temporarily disabled until moving to new IR finished.
} }
private fun lowerFile(irFile: IrFile) { private fun lowerFile(irFile: IrFile) {
@@ -16,12 +16,21 @@
package org.jetbrains.kotlin.backend.konan 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.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.descriptors.*
import org.jetbrains.kotlin.incremental.components.NoLookupLocation 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.FqName
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.ExternalOverridabilityCondition import org.jetbrains.kotlin.resolve.ExternalOverridabilityCondition
import org.jetbrains.kotlin.resolve.constants.BooleanValue
import org.jetbrains.kotlin.resolve.descriptorUtil.* import org.jetbrains.kotlin.resolve.descriptorUtil.*
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils 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")) private val objCClassFqName = interopPackageName.child(Name.identifier("ObjCClass"))
internal val externalObjCClassFqName = interopPackageName.child(Name.identifier("ExternalObjCClass")) internal val externalObjCClassFqName = interopPackageName.child(Name.identifier("ExternalObjCClass"))
private val objCMethodFqName = interopPackageName.child(Name.identifier("ObjCMethod")) 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")) private val objCBridgeFqName = interopPackageName.child(Name.identifier("ObjCBridge"))
fun ClassDescriptor.isObjCClass(): Boolean = fun ClassDescriptor.isObjCClass(): Boolean =
@@ -62,8 +73,7 @@ fun FunctionDescriptor.canObjCClassMethodBeCalledVirtually(overriddenDescriptor:
fun ClassDescriptor.isKotlinObjCClass(): Boolean = this.isObjCClass() && !this.isExternalObjCClass() fun ClassDescriptor.isKotlinObjCClass(): Boolean = this.isObjCClass() && !this.isExternalObjCClass()
data class ObjCMethodInfo(val descriptor: FunctionDescriptor, data class ObjCMethodInfo(val bridge: FunctionDescriptor,
val bridge: FunctionDescriptor,
val selector: String, val selector: String,
val encoding: String, val encoding: String,
val imp: String) val imp: String)
@@ -75,19 +85,22 @@ private fun FunctionDescriptor.decodeObjCMethodAnnotation(): ObjCMethodInfo? {
assert (this.kind.isReal) assert (this.kind.isReal)
val methodAnnotation = this.annotations.findAnnotation(objCMethodFqName) ?: return null val methodAnnotation = this.annotations.findAnnotation(objCMethodFqName) ?: return null
val packageFragment = this.parents.filterIsInstance<PackageFragmentDescriptor>().single() val packageFragment = this.findPackage()
val bridgeName = methodAnnotation.getStringValue("bridge") val bridgeName = methodAnnotation.getStringValue("bridge")
return objCMethodInfoByBridge(packageFragment, bridgeName)
}
private fun objCMethodInfoByBridge(packageFragment: PackageFragmentDescriptor, bridgeName: String): ObjCMethodInfo {
val bridge = packageFragment.getMemberScope() val bridge = packageFragment.getMemberScope()
.getContributedFunctions(Name.identifier(bridgeName), NoLookupLocation.FROM_BACKEND) .getContributedFunctions(Name.identifier(bridgeName), NoLookupLocation.FROM_BACKEND)
.single() .single()
val bridgeAnnotation = bridge.getBridgeAnnotation()!! val bridgeAnnotation = bridge.getBridgeAnnotation()!!
return ObjCMethodInfo( return ObjCMethodInfo(
descriptor = this,
bridge = bridge, bridge = bridge,
selector = methodAnnotation.getStringValue("selector"), selector = bridgeAnnotation.getStringValue("selector"),
encoding = bridgeAnnotation.getStringValue("encoding"), encoding = bridgeAnnotation.getStringValue("encoding"),
imp = bridgeAnnotation.getStringValue("imp") imp = bridgeAnnotation.getStringValue("imp")
) )
@@ -112,6 +125,14 @@ fun FunctionDescriptor.getExternalObjCMethodInfo(): ObjCMethodInfo? = this.getOb
fun FunctionDescriptor.getObjCMethodInfo(): ObjCMethodInfo? = this.getObjCMethodInfo(onlyExternal = false) 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. * 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? { fun ConstructorDescriptor.getObjCInitMethod(): FunctionDescriptor? {
return this.annotations.findAnnotation(FqName("kotlinx.cinterop.ObjCConstructor"))?.let { return this.annotations.findAnnotation(objCConstructorFqName)?.let {
val initSelector = it.getStringValue("initSelector") val initSelector = it.getStringValue("initSelector")
this.constructedClass.unsubstitutedMemberScope.getContributedDescriptors().asSequence() this.constructedClass.unsubstitutedMemberScope.getContributedDescriptors().asSequence()
.filterIsInstance<FunctionDescriptor>() .filterIsInstance<FunctionDescriptor>()
@@ -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<IrSimpleFunction>()
.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()) { fun inferObjCSelector(descriptor: FunctionDescriptor): String = if (descriptor.valueParameters.isEmpty()) {
descriptor.name.asString() descriptor.name.asString()
} else { } else {
@@ -119,6 +119,8 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable): Sym
val interopObjCObjectSuperInitCheck = val interopObjCObjectSuperInitCheck =
symbolTable.referenceSimpleFunction(context.interopBuiltIns.objCObjectSuperInitCheck) symbolTable.referenceSimpleFunction(context.interopBuiltIns.objCObjectSuperInitCheck)
val interopObjCObjectInitBy = symbolTable.referenceSimpleFunction(context.interopBuiltIns.objCObjectInitBy)
val interopObjCObjectRawValueGetter = val interopObjCObjectRawValueGetter =
symbolTable.referenceSimpleFunction(context.interopBuiltIns.objCObjectRawPtr) symbolTable.referenceSimpleFunction(context.interopBuiltIns.objCObjectRawPtr)
@@ -41,7 +41,6 @@ internal val IrField.isDelegate get() = @Suppress("DEPRECATION") this.descriptor
internal fun IrFunction.getObjCMethodInfo() = this.descriptor.getObjCMethodInfo() internal fun IrFunction.getObjCMethodInfo() = this.descriptor.getObjCMethodInfo()
internal fun IrClass.isExternalObjCClass() = this.descriptor.isExternalObjCClass() internal fun IrClass.isExternalObjCClass() = this.descriptor.isExternalObjCClass()
internal fun IrClass.isKotlinObjCClass() = this.descriptor.isKotlinObjCClass() internal fun IrClass.isKotlinObjCClass() = this.descriptor.isKotlinObjCClass()
internal fun IrConstructor.getObjCInitMethod() = this.descriptor.getObjCInitMethod()
internal fun IrFunction.getExternalObjCMethodInfo() = this.descriptor.getExternalObjCMethodInfo() internal fun IrFunction.getExternalObjCMethodInfo() = this.descriptor.getExternalObjCMethodInfo()
internal fun IrFunction.isObjCClassMethod() = this.descriptor.isObjCClassMethod() internal fun IrFunction.isObjCClassMethod() = this.descriptor.isObjCClassMethod()
internal fun IrFunction.canObjCClassMethodBeCalledVirtually(overridden: IrFunction) = internal fun IrFunction.canObjCClassMethodBeCalledVirtually(overridden: IrFunction) =
@@ -503,7 +503,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
return return
} }
if (declaration.descriptor.getObjCInitMethod() != null) { if (declaration.isObjCConstructor()) {
// Do not generate any ctors for external Objective-C classes. // Do not generate any ctors for external Objective-C classes.
return return
} }
@@ -387,12 +387,12 @@ private class DeclarationsGeneratorVisitor(override val context: Context) :
val descriptor = declaration val descriptor = declaration
val llvmFunctionType = getLlvmFunctionType(descriptor) val llvmFunctionType = getLlvmFunctionType(descriptor)
if ((descriptor is ConstructorDescriptor && descriptor.getObjCInitMethod() != null)) { if ((descriptor is ConstructorDescriptor && descriptor.isObjCConstructor())) {
return return
} }
val llvmFunction = if (descriptor.isExternal) { val llvmFunction = if (descriptor.isExternal) {
if (descriptor.isIntrinsic || descriptor.getExternalObjCMethodInfo() != null) { if (descriptor.isIntrinsic || descriptor.isObjCBridgeBased()) {
return return
} }
@@ -26,6 +26,9 @@ import org.jetbrains.kotlin.backend.konan.*
import org.jetbrains.kotlin.backend.konan.descriptors.allOverriddenDescriptors import org.jetbrains.kotlin.backend.konan.descriptors.allOverriddenDescriptors
import org.jetbrains.kotlin.backend.konan.descriptors.isInterface import org.jetbrains.kotlin.backend.konan.descriptors.isInterface
import org.jetbrains.kotlin.backend.konan.descriptors.synthesizedName import org.jetbrains.kotlin.backend.konan.descriptors.synthesizedName
import org.jetbrains.kotlin.backend.konan.irasdescriptors.constructors
import org.jetbrains.kotlin.backend.konan.irasdescriptors.getObjCMethodInfo
import org.jetbrains.kotlin.backend.konan.irasdescriptors.getSuperClassNotAny
import org.jetbrains.kotlin.backend.konan.irasdescriptors.isReal import org.jetbrains.kotlin.backend.konan.irasdescriptors.isReal
import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.*
@@ -72,11 +75,10 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme
topLevelInitializers.clear() topLevelInitializers.clear()
} }
private fun IrBuilderWithScope.callAlloc(classSymbol: IrClassSymbol): IrExpression { private fun IrBuilderWithScope.callAlloc(classPtr: IrExpression): IrExpression =
return irCall(symbols.interopAllocObjCObject, listOf(classSymbol.descriptor.defaultType)).apply { irCall(symbols.interopAllocObjCObject).apply {
putValueArgument(0, getObjCClass(classSymbol)) putValueArgument(0, classPtr)
} }
}
private fun IrBuilderWithScope.getObjCClass(classSymbol: IrClassSymbol): IrExpression { private fun IrBuilderWithScope.getObjCClass(classSymbol: IrClassSymbol): IrExpression {
val classDescriptor = classSymbol.descriptor val classDescriptor = classSymbol.descriptor
@@ -104,7 +106,7 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme
val interop = context.interopBuiltIns val interop = context.interopBuiltIns
irClass.declarations.mapNotNull { irClass.declarations.toList().mapNotNull {
when { when {
it is IrSimpleFunction && it.descriptor.annotations.hasAnnotation(interop.objCAction) -> it is IrSimpleFunction && it.descriptor.annotations.hasAnnotation(interop.objCAction) ->
generateActionImp(it) generateActionImp(it)
@@ -112,6 +114,9 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme
it is IrProperty && it.descriptor.annotations.hasAnnotation(interop.objCOutlet) -> it is IrProperty && it.descriptor.annotations.hasAnnotation(interop.objCOutlet) ->
generateOutletSetterImp(it) generateOutletSetterImp(it)
it is IrConstructor && it.descriptor.annotations.hasAnnotation(interop.objCOverrideInit) ->
generateOverrideInit(irClass, it)
else -> null else -> null
} }
}.let { irClass.addChildren(it) } }.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<TypeParameterDescriptor>(),
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 { private fun generateActionImp(function: IrSimpleFunction): IrSimpleFunction {
val action = "@${context.interopBuiltIns.objCAction.name}" val action = "@${context.interopBuiltIns.objCAction.name}"
@@ -396,6 +517,15 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme
assert(constructedClassDescriptor.getSuperClassNotAny() == expression.descriptor.constructedClass) assert(constructedClassDescriptor.getSuperClassNotAny() == expression.descriptor.constructedClass)
val initMethod = expression.descriptor.getObjCInitMethod()!! 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()!! val initMethodInfo = initMethod.getExternalObjCMethodInfo()!!
assert(expression.dispatchReceiver == null) assert(expression.dispatchReceiver == null)
@@ -457,24 +587,25 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme
assert(expression.extensionReceiver == null) assert(expression.extensionReceiver == null)
assert(expression.dispatchReceiver == null) assert(expression.dispatchReceiver == null)
return builder.irBlock(expression) { val constructedClass = descriptor.constructedClass
val allocated = irTemporaryVar(callAlloc(symbolTable.referenceClass(descriptor.constructedClass))) val initMethodInfo = initMethod.getExternalObjCMethodInfo()!!
val result = irTemporaryVar( return builder.at(expression).run {
genLoweredObjCMethodCall( val classPtr = getObjCClass(symbolTable.referenceClass(constructedClass))
initMethod.getExternalObjCMethodInfo()!!, irForceNotNull(callAllocAndInit(classPtr, initMethodInfo, arguments))
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)
} }
} }
} }
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 -> descriptor.getExternalObjCMethodInfo()?.let { methodInfo ->
val isInteropStubsFile = val isInteropStubsFile =
currentFile.fileAnnotations.any { it.fqName == FqName("kotlinx.cinterop.InteropStubs") } 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>
): 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) = private fun IrBuilderWithScope.getRawPtr(receiver: IrExpression) =
irCall(symbols.interopObjCObjectRawValueGetter).apply { irCall(symbols.interopObjCObjectRawValueGetter).apply {
extensionReceiver = receiver extensionReceiver = receiver
@@ -24,12 +24,10 @@ import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.descriptors.impl.PropertyDescriptorImpl import org.jetbrains.kotlin.descriptors.impl.PropertyDescriptorImpl
import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.* import org.jetbrains.kotlin.ir.declarations.impl.*
import org.jetbrains.kotlin.ir.expressions.IrConst import org.jetbrains.kotlin.ir.expressions.*
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.impl.IrExpressionBodyImpl import org.jetbrains.kotlin.ir.expressions.impl.IrExpressionBodyImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrTypeOperatorCallImpl import org.jetbrains.kotlin.ir.expressions.impl.IrTypeOperatorCallImpl
import org.jetbrains.kotlin.ir.symbols.impl.IrTypeParameterSymbolImpl 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.OverridingUtil
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils
import java.lang.reflect.Proxy import java.lang.reflect.Proxy
//TODO: delete file on next kotlin dependency update //TODO: delete file on next kotlin dependency update
@@ -375,3 +374,19 @@ internal fun KonanBackendContext.report(declaration: IrDeclaration, message: Str
) )
if (isError) throw KonanCompilationException() 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)
)
}
}
+5 -5
View File
@@ -13,10 +13,11 @@ fun main(args: Array<String>) {
} }
} }
class AppDelegate : UIResponder(), UIApplicationDelegateProtocol { class AppDelegate : UIResponder, UIApplicationDelegateProtocol {
companion object : UIResponderMeta(), UIApplicationDelegateProtocolMeta {}
override fun init() = initBy(AppDelegate()) @OverrideInit constructor() : super()
companion object : UIResponderMeta(), UIApplicationDelegateProtocolMeta {}
private var _window: UIWindow? = null private var _window: UIWindow? = null
override fun window() = _window override fun window() = _window
@@ -26,8 +27,7 @@ class AppDelegate : UIResponder(), UIApplicationDelegateProtocol {
@ExportObjCClass @ExportObjCClass
class ViewController : UIViewController { class ViewController : UIViewController {
constructor(aDecoder: NSCoder) : super(aDecoder) @OverrideInit constructor(coder: NSCoder) : super(coder)
override fun initWithCoder(aDecoder: NSCoder) = initBy(ViewController(aDecoder))
@ObjCOutlet @ObjCOutlet
lateinit var label: UILabel lateinit var label: UILabel