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)
}
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
@@ -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)
@@ -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) }
@@ -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)}"
}
@@ -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
/**
@@ -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)
}
}
@@ -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")
@@ -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) {
@@ -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 {
@@ -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)
@@ -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) =
@@ -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
}
@@ -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
}
@@ -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
@@ -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)
)
}
}
+5 -5
View File
@@ -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