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:
committed by
SvyatoslavScherbina
parent
688b3f26f2
commit
8371ac137f
+6
-2
@@ -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<CXCursor>, name: String): Boolean {
|
||||
var result = false
|
||||
|
||||
+1
-1
@@ -143,7 +143,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 isOptional: Boolean, val isInit: Boolean
|
||||
val isOptional: Boolean, val isInit: Boolean, val isDesginatedInitializer: Boolean
|
||||
) {
|
||||
|
||||
fun returnsInstancetype(): Boolean = returnType is ObjCInstanceType
|
||||
|
||||
@@ -20,16 +20,25 @@ package kotlinx.cinterop
|
||||
|
||||
interface ObjCObject
|
||||
interface ObjCClass : ObjCObject
|
||||
interface ObjCClassOf<T : ObjCObject> : 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 : ObjCObjectBase> 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)
|
||||
|
||||
+7
@@ -83,3 +83,10 @@ val annotationForUnableToImport
|
||||
|
||||
fun String.applyToStrings(vararg arguments: String) =
|
||||
"${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) }
|
||||
|
||||
+5
@@ -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)}"
|
||||
}
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
/**
|
||||
|
||||
+75
-25
@@ -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<String> {
|
||||
private fun ObjCMethod.getKotlinParameterNames(forConstructorOrFactory: Boolean = false): List<String> {
|
||||
val selectorParts = this.selector.split(":")
|
||||
|
||||
val result = mutableListOf<String>()
|
||||
@@ -41,10 +41,8 @@ private fun ObjCMethod.getKotlinParameterNames(): List<String> {
|
||||
}
|
||||
|
||||
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<String> {
|
||||
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) + "<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(
|
||||
@@ -85,7 +133,8 @@ class ObjCMethodStub(stubGenerator: StubGenerator,
|
||||
}
|
||||
|
||||
private val bodyLines: List<String>
|
||||
private val joinedKotlinParameters: String
|
||||
private val kotlinParameters: List<KotlinParameter>
|
||||
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<Pair<String, KotlinType>>()
|
||||
val kotlinObjCBridgeParameters = mutableListOf<Pair<String, KotlinType>>()
|
||||
kotlinParameters = mutableListOf()
|
||||
val kotlinObjCBridgeParameters = mutableListOf<KotlinParameter>()
|
||||
val nativeBridgeArguments = mutableListOf<TypedKotlinValue>()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -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")
|
||||
|
||||
+1
-1
@@ -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) {
|
||||
|
||||
+52
-6
@@ -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<PackageFragmentDescriptor>().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<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()) {
|
||||
descriptor.name.asString()
|
||||
} else {
|
||||
|
||||
+2
@@ -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)
|
||||
|
||||
|
||||
-1
@@ -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) =
|
||||
|
||||
+1
-1
@@ -503,7 +503,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
||||
return
|
||||
}
|
||||
|
||||
if (declaration.descriptor.getObjCInitMethod() != null) {
|
||||
if (declaration.isObjCConstructor()) {
|
||||
// Do not generate any ctors for external Objective-C classes.
|
||||
return
|
||||
}
|
||||
|
||||
+2
-2
@@ -387,12 +387,12 @@ private class DeclarationsGeneratorVisitor(override val context: Context) :
|
||||
val descriptor = declaration
|
||||
val llvmFunctionType = getLlvmFunctionType(descriptor)
|
||||
|
||||
if ((descriptor is ConstructorDescriptor && descriptor.getObjCInitMethod() != null)) {
|
||||
if ((descriptor is ConstructorDescriptor && descriptor.isObjCConstructor())) {
|
||||
return
|
||||
}
|
||||
|
||||
val llvmFunction = if (descriptor.isExternal) {
|
||||
if (descriptor.isIntrinsic || descriptor.getExternalObjCMethodInfo() != null) {
|
||||
if (descriptor.isIntrinsic || descriptor.isObjCBridgeBased()) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+173
-20
@@ -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.isInterface
|
||||
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.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
@@ -72,11 +75,10 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme
|
||||
topLevelInitializers.clear()
|
||||
}
|
||||
|
||||
private fun IrBuilderWithScope.callAlloc(classSymbol: IrClassSymbol): IrExpression {
|
||||
return irCall(symbols.interopAllocObjCObject, listOf(classSymbol.descriptor.defaultType)).apply {
|
||||
putValueArgument(0, getObjCClass(classSymbol))
|
||||
}
|
||||
}
|
||||
private fun IrBuilderWithScope.callAlloc(classPtr: IrExpression): IrExpression =
|
||||
irCall(symbols.interopAllocObjCObject).apply {
|
||||
putValueArgument(0, classPtr)
|
||||
}
|
||||
|
||||
private fun IrBuilderWithScope.getObjCClass(classSymbol: IrClassSymbol): IrExpression {
|
||||
val classDescriptor = classSymbol.descriptor
|
||||
@@ -104,7 +106,7 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme
|
||||
|
||||
val interop = context.interopBuiltIns
|
||||
|
||||
irClass.declarations.mapNotNull {
|
||||
irClass.declarations.toList().mapNotNull {
|
||||
when {
|
||||
it is IrSimpleFunction && it.descriptor.annotations.hasAnnotation(interop.objCAction) ->
|
||||
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<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 {
|
||||
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>
|
||||
): 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
|
||||
|
||||
+19
-4
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,10 +13,11 @@ fun main(args: Array<String>) {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user