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:
committed by
SvyatoslavScherbina
parent
f986a0cb10
commit
7bebb8d960
+2
-2
@@ -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
|
||||
|
||||
+8
@@ -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 }
|
||||
}
|
||||
|
||||
+26
@@ -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)
|
||||
|
||||
+71
-10
@@ -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)
|
||||
|
||||
+22
-7
@@ -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) {
|
||||
|
||||
+70
-4
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user