Expose object instances and enum entries to produced framework

#KT-21892 Fixed.
Fix #1165

Also handle some clashes with NSObject method names.
This commit is contained in:
Svyatoslav Scherbina
2018-01-09 17:06:38 +03:00
committed by SvyatoslavScherbina
parent f986a0cb10
commit 7bebb8d960
6 changed files with 199 additions and 23 deletions
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.backend.konan
import org.jetbrains.kotlin.backend.jvm.descriptors.initialize
import org.jetbrains.kotlin.backend.konan.descriptors.synthesizedName
import org.jetbrains.kotlin.backend.common.ir.createSimpleDelegatingConstructorDescriptor
import org.jetbrains.kotlin.backend.konan.descriptors.enumEntries
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.descriptors.impl.*
@@ -127,8 +128,7 @@ internal class EnumSpecialDeclarationsFactory(val context: Context) {
private fun createEnumEntriesMap(enumClassDescriptor: ClassDescriptor): Map<Name, Int> {
val map = mutableMapOf<Name, Int>()
enumClassDescriptor.unsubstitutedMemberScope.getContributedDescriptors()
.filter { it is ClassDescriptor && it.kind == ClassKind.ENUM_ENTRY }
enumClassDescriptor.enumEntries
.sortedBy { it.name }
.forEachIndexed { index, entry -> map.put(entry.name, index) }
return map
@@ -336,3 +336,11 @@ fun ModuleDescriptor.getPackageFragments(): List<PackageFragmentDescriptor> =
getPackagesFqNames(this).flatMap {
getPackage(it).fragments.filter { it.module == this }
}
val ClassDescriptor.enumEntries: List<ClassDescriptor>
get() {
assert(this.kind == ClassKind.ENUM_CLASS)
return this.unsubstitutedMemberScope.getContributedDescriptors()
.filterIsInstance<ClassDescriptor>()
.filter { it.kind == ClassKind.ENUM_ENTRY }
}
@@ -27,6 +27,7 @@ import org.jetbrains.kotlin.backend.konan.isObjCClass
import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
internal class CodeGenerator(override val context: Context) : ContextUtils {
@@ -596,6 +597,31 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
return valuePhi
}
/**
* Note: the same code is generated as IR in [org.jetbrains.kotlin.backend.konan.lower.EnumUsageLowering].
*/
fun getEnumEntry(descriptor: ClassDescriptor, exceptionHandler: ExceptionHandler): LLVMValueRef {
assert(descriptor.kind == ClassKind.ENUM_ENTRY)
val enumClassDescriptor = descriptor.containingDeclaration as ClassDescriptor
val loweredEnum = context.specialDeclarationsFactory.getLoweredEnum(enumClassDescriptor)
val ordinal = loweredEnum.entriesMap[descriptor.name]!!
val values = call(
loweredEnum.valuesGetter.descriptor.original.llvmFunction,
emptyList(),
Lifetime.ARGUMENT,
exceptionHandler
)
return call(
loweredEnum.itemGetterDescriptor.original.llvmFunction,
listOf(values, Int32(ordinal).llvm),
Lifetime.GLOBAL,
exceptionHandler
)
}
fun resetDebugLocation() {
if (!context.shouldContainDebugInfo()) return
if (!currentPositionHolder.isAfterTerminator)
@@ -137,6 +137,10 @@ internal class ObjCExportCodeGenerator(
}
}
fun FunctionGenerationContext.initRuntimeIfNeeded() {
callFromBridge(context.llvm.initRuntimeIfNeeded, emptyList())
}
inline fun FunctionGenerationContext.convertKotlin(
genValue: (Lifetime) -> LLVMValueRef,
actualType: KotlinType,
@@ -440,8 +444,7 @@ private fun ObjCExportCodeGenerator.generateObjCImp(
// TODO: check for abstract class if it is a constructor.
if (methodBridge.isKotlinTopLevel) {
callFromBridge(context.llvm.initRuntimeIfNeeded, emptyList())
// For instance methods it gets called when allocating.
initRuntimeIfNeeded() // For instance methods it gets called when allocating.
}
if (target == null) {
@@ -494,7 +497,7 @@ private fun ObjCExportCodeGenerator.generateObjCImpForArrayConstructor(
val result = LLVMAddFunction(context.llvmModule, "", objCFunctionType(methodBridge))!!
generateFunction(codegen, result) {
callFromBridge(context.llvm.initRuntimeIfNeeded, emptyList())
initRuntimeIfNeeded() // For instance methods it gets called when allocating.
val kotlinValueArgs = methodBridge.paramBridges
.drop(1) // Drop class method receiver.
@@ -786,8 +789,22 @@ private fun ObjCExportCodeGenerator.createTypeAdapter(
emptyList()
}
if (descriptor.isUnit()) {
classAdapters += createUnitInstanceAdapter()
when (descriptor.kind) {
ClassKind.OBJECT -> {
classAdapters += if (descriptor.isUnit()) {
createUnitInstanceAdapter()
} else {
createObjectInstanceAdapter(descriptor)
}
}
ClassKind.ENUM_CLASS -> {
descriptor.enumEntries.mapTo(classAdapters) {
createEnumEntryAdapter(it)
}
}
else -> {
// Nothing special.
}
}
return ObjCTypeAdapter(
@@ -832,13 +849,14 @@ private fun ObjCExportCodeGenerator.createDirectAdapters(
}
}
private fun ObjCExportCodeGenerator.createUnitInstanceAdapter(): ObjCExportCodeGenerator.ObjCToKotlinMethodAdapter {
val selector = "unit"
val methodBridge = MethodBridge(ReferenceBridge, listOf(ReferenceBridge, ReferenceBridge))
private inline fun ObjCExportCodeGenerator.generateObjCToKotlinMethodAdapter(
methodBridge: MethodBridge,
selector: String,
block: FunctionGenerationContext.() -> Unit
): ObjCExportCodeGenerator.ObjCToKotlinMethodAdapter {
val encoding = getEncoding(methodBridge)
val imp = generateFunction(codegen, objCFunctionType(methodBridge), "") {
ret(callFromBridge(context.llvm.Kotlin_ObjCExport_convertUnit, listOf(codegen.theUnitInstanceRef.llvm)))
block()
}
LLVMSetLinkage(imp, LLVMLinkage.LLVMPrivateLinkage)
@@ -846,6 +864,49 @@ private fun ObjCExportCodeGenerator.createUnitInstanceAdapter(): ObjCExportCodeG
return ObjCToKotlinMethodAdapter(selector, encoding, constPointer(imp))
}
private fun ObjCExportCodeGenerator.createUnitInstanceAdapter() =
generateObjCToKotlinMethodAdapter(
MethodBridge(ReferenceBridge, listOf(ReferenceBridge, ReferenceBridge)),
namer.getObjectInstanceSelector(context.builtIns.unit)
) {
initRuntimeIfNeeded() // For instance methods it gets called when allocating.
ret(callFromBridge(context.llvm.Kotlin_ObjCExport_convertUnit, listOf(codegen.theUnitInstanceRef.llvm)))
}
private fun ObjCExportCodeGenerator.createObjectInstanceAdapter(
descriptor: ClassDescriptor
): ObjCExportCodeGenerator.ObjCToKotlinMethodAdapter {
assert(descriptor.kind == ClassKind.OBJECT)
assert(!descriptor.isUnit())
val selector = namer.getObjectInstanceSelector(descriptor)
val methodBridge = MethodBridge(ReferenceBridge, listOf(ReferenceBridge, ReferenceBridge))
return generateObjCToKotlinMethodAdapter(methodBridge, selector) {
initRuntimeIfNeeded() // For instance methods it gets called when allocating.
val value = getObjectValue(descriptor, ExceptionHandler.Caller, locationInfo = null)
ret(kotlinToObjC(value, ReferenceBridge))
}
}
private fun ObjCExportCodeGenerator.createEnumEntryAdapter(
descriptor: ClassDescriptor
): ObjCExportCodeGenerator.ObjCToKotlinMethodAdapter {
assert(descriptor.kind == ClassKind.ENUM_ENTRY)
val selector = namer.getEnumEntrySelector(descriptor)
val methodBridge = MethodBridge(ReferenceBridge, listOf(ReferenceBridge, ReferenceBridge))
return generateObjCToKotlinMethodAdapter(methodBridge, selector) {
initRuntimeIfNeeded() // For instance methods it gets called when allocating.
val value = getEnumEntry(descriptor, ExceptionHandler.Caller)
ret(kotlinToObjC(value, ReferenceBridge))
}
}
private fun List<CallableMemberDescriptor>.toMethods(): List<FunctionDescriptor> = this.flatMap {
when (it) {
is PropertyDescriptor -> listOfNotNull(it.getter, it.setter)
@@ -18,10 +18,10 @@ package org.jetbrains.kotlin.backend.konan.objcexport
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.KonanCompilationException
import org.jetbrains.kotlin.backend.konan.descriptors.enumEntries
import org.jetbrains.kotlin.backend.konan.descriptors.getPackageFragments
import org.jetbrains.kotlin.backend.konan.descriptors.isArray
import org.jetbrains.kotlin.backend.konan.descriptors.isInterface
import org.jetbrains.kotlin.backend.konan.descriptors.isUnit
import org.jetbrains.kotlin.backend.konan.reportCompilationError
import org.jetbrains.kotlin.builtins.getReceiverTypeFromFunctionType
import org.jetbrains.kotlin.builtins.getReturnTypeFromFunctionType
@@ -203,11 +203,6 @@ internal class ObjCExportHeaderGenerator(val context: Context) {
+"@interface $name : $superName${descriptor.superProtocolsClause}"
if (descriptor.isUnit()) {
+"+ (instancetype)unit NS_SWIFT_NAME(init());"
+""
}
val presentConstructors = mutableSetOf<String>()
descriptor.constructors.filter { mapper.shouldBeExposed(it) }.forEach {
@@ -217,12 +212,32 @@ internal class ObjCExportHeaderGenerator(val context: Context) {
+""
}
if (descriptor.isArray) {
if (descriptor.isArray || descriptor.kind == ClassKind.OBJECT || descriptor.kind == ClassKind.ENUM_CLASS) {
+"+(instancetype)alloc __attribute__((unavailable));"
+"+(instancetype)allocWithZone:(struct _NSZone *)zone __attribute__((unavailable));"
+""
}
// TODO: consider adding exception-throwing impls for these.
when (descriptor.kind) {
ClassKind.OBJECT -> {
+"+(instancetype)${namer.getObjectInstanceSelector(descriptor)} NS_SWIFT_NAME(init());"
+""
}
ClassKind.ENUM_CLASS -> {
val type = mapType(descriptor.defaultType, ReferenceBridge)
descriptor.enumEntries.forEach {
val entryName = namer.getEnumEntrySelector(it)
+"@property (class, readonly) ${type.render(entryName)};"
}
+""
}
else -> {
// Nothing special.
}
}
// Hide "unimplemented" super constructors:
superClass?.constructors?.filter { mapper.shouldBeExposed(it) }?.forEach {
if (getSelector(it) !in presentConstructors) {
@@ -35,6 +35,17 @@ internal class ObjCExportNamer(val context: Context, val mapper: ObjCExportMappe
private val topLevelNamePrefix = context.moduleDescriptor.namePrefix
private val methodSelectors = object : Mapping<FunctionDescriptor, String>() {
// Try to avoid clashing with critical NSObject instance methods:
private val reserved = setOf(
"retain", "release", "autorelease",
"class", "superclass",
"hash"
)
override fun reserved(name: String) = name in reserved
override fun conflict(first: FunctionDescriptor, second: FunctionDescriptor): Boolean =
!mapper.canHaveSameSelector(first, second)
}
@@ -58,6 +69,30 @@ internal class ObjCExportNamer(val context: Context, val mapper: ObjCExportMappe
override fun conflict(first: Any, second: Any): Boolean = true
}
private abstract inner class ClassPropertyNameMapping<T : Any> : Mapping<T, String>() {
// Try to avoid clashing with NSObject class methods:
private val reserved = setOf(
"retain", "release", "autorelease",
"initialize", "load", "alloc", "new", "class", "superclass",
"classFallbacksForKeyedArchiver", "classForKeyedUnarchiver",
"description", "debugDescription", "version", "hash",
"useStoredAccessor"
)
override fun reserved(name: String) = name in reserved
}
private val objectInstanceSelectors = object : ClassPropertyNameMapping<ClassDescriptor>() {
override fun conflict(first: ClassDescriptor, second: ClassDescriptor) = false
}
private val enumEntrySelectors = object : ClassPropertyNameMapping<ClassDescriptor>() {
override fun conflict(first: ClassDescriptor, second: ClassDescriptor) =
first.containingDeclaration == second.containingDeclaration
}
fun getPackageName(fqName: FqName): String = classNames.getOrPut(fqName) {
StringBuilder().apply {
append(topLevelNamePrefix)
@@ -168,6 +203,30 @@ internal class ObjCExportNamer(val context: Context, val mapper: ObjCExportMappe
}
}
fun getObjectInstanceSelector(descriptor: ClassDescriptor): String {
assert(descriptor.kind == ClassKind.OBJECT)
return objectInstanceSelectors.getOrPut(descriptor) {
val name = descriptor.name.asString().decapitalize().mangleIfSpecialFamily("get")
StringBuilder(name).mangledSequence { append("_") }
}
}
fun getEnumEntrySelector(descriptor: ClassDescriptor): String {
assert(descriptor.kind == ClassKind.ENUM_ENTRY)
return enumEntrySelectors.getOrPut(descriptor) {
// FOO_BAR_BAZ -> fooBarBaz:
val name = descriptor.name.asString().split('_').mapIndexed { index, s ->
val lower = s.toLowerCase()
if (index == 0) lower else lower.capitalize()
}.joinToString("").mangleIfSpecialFamily("the")
StringBuilder(name).mangledSequence { append("_") }
}
}
init {
val any = context.builtIns.any
@@ -204,18 +263,22 @@ internal class ObjCExportNamer(val context: Context, val mapper: ObjCExportMappe
else -> this.name.asString()
}
val trimmedCandidate = candidate.dropWhile { it == '_' }
return candidate.mangleIfSpecialFamily("do")
}
private fun String.mangleIfSpecialFamily(prefix: String): String {
val trimmed = this.dropWhile { it == '_' }
for (family in listOf("alloc", "copy", "mutableCopy", "new", "init")) {
if (trimmedCandidate.startsWithWords(family)) {
if (trimmed.startsWithWords(family)) {
// Then method can be detected as having special family by Objective-C compiler.
// mangle the name:
return "do" + candidate.capitalize()
return prefix + this.capitalize()
}
}
// TODO: handle clashes with NSObject methods etc.
return candidate
return this
}
private fun String.startsWithWords(words: String) = this.startsWith(words) &&
@@ -226,6 +289,7 @@ internal class ObjCExportNamer(val context: Context, val mapper: ObjCExportMappe
private val nameToElements = mutableMapOf<N, MutableList<T>>()
abstract fun conflict(first: T, second: T): Boolean
open fun reserved(name: N) = false
fun getOrPut(element: T, nameCandidates: () -> Sequence<N>): N {
getIfAssigned(element)?.let { return it }
@@ -244,6 +308,8 @@ internal class ObjCExportNamer(val context: Context, val mapper: ObjCExportMappe
fun tryAssign(element: T, name: N): Boolean {
if (element in elementToName) error(element)
if (reserved(name)) return false
val elements = nameToElements.getOrPut(name) { mutableListOf() }
if (elements.any { conflict(element, it) }) {
return false