[ObjCExport] Add initial objective-c function translation

KT-64226
This commit is contained in:
eugene.levenetc
2024-01-08 13:51:42 +01:00
committed by Space Team
parent 7e61f9cb42
commit 92affa1b24
13 changed files with 1077 additions and 32 deletions
@@ -15,4 +15,11 @@ data class KtObjCExportConfiguration(
* Should kdoc written by the user be available in the generated headers and stubs
*/
val exportKDoc: Boolean = true,
/**
* Used to handle Swift outer and inner classes naming edge cases, see:
* - [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamingHelper.canBeSwiftOuter]
* - [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamingHelper.canBeSwiftInner]
*/
val objcGenerics: Boolean = true
)
@@ -0,0 +1,24 @@
package org.jetbrains.kotlin.objcexport.analysisApiUtils
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.objcexport.KtObjCExportSession
context(KtAnalysisSession, KtObjCExportSession)
internal fun KtClassOrObjectSymbol.members(): List<KtSymbol> {
return getMemberScope()
.getAllSymbols()
.sortedBy { sortMembers(it) }
.filterIsInstance<KtCallableSymbol>()
.filter { member -> member.isVisibleInObjC() }
.toList()
}
/**
* Temp workaround of [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorKt.makeMethodsOrderStable]
*/
private fun sortMembers(it: KtDeclarationSymbol) = when (it) {
is KtConstructorSymbol -> 0
is KtFunctionSymbol -> 1
else -> 2
}
@@ -0,0 +1,25 @@
package org.jetbrains.kotlin.objcexport
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.symbols.KtConstructorSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionLikeSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtPropertyGetterSymbol
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportFunctionName
context(KtAnalysisSession, KtObjCExportSession)
fun KtFunctionLikeSymbol.getObjCFunctionName(): ObjCExportFunctionName {
val resolveObjCNameAnnotation = resolveObjCNameAnnotation()
val name: String = when (this) {
is KtFunctionSymbol -> this.name.asString()
is KtConstructorSymbol -> "init"
is KtPropertyGetterSymbol -> "get" //TODO: implement properly getter since it doesn't have [name]
else -> "Undefined name for $this type"
}
return ObjCExportFunctionName(
resolveObjCNameAnnotation?.swiftName ?: name, resolveObjCNameAnnotation?.objCName ?: name
)
}
@@ -0,0 +1,13 @@
package org.jetbrains.kotlin.objcexport
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportClassOrProtocolName
/**
* @return The ObjC/Swift name of a class or protocol present in the Kotlin Stdlib.
* @receiver The name of the Kotlin class or protocol
*/
context(KtObjCExportSession)
internal fun String.getObjCKotlinStdlibClassOrProtocolName() = ObjCExportClassOrProtocolName(
swiftName = "Kotlin$this",
objCName = "${configuration.frameworkName.orEmpty()}$this"
)
@@ -0,0 +1,75 @@
package org.jetbrains.kotlin.objcexport
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.annotations.KtAnnotationsList
import org.jetbrains.kotlin.analysis.api.symbols.KtClassKind
import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolWithModality
import org.jetbrains.kotlin.backend.konan.KonanFqNames
import org.jetbrains.kotlin.backend.konan.objcexport.*
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.objcexport.analysisApiUtils.getSuperClassSymbolNotAny
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC
import org.jetbrains.kotlin.objcexport.analysisApiUtils.members
context(KtAnalysisSession, KtObjCExportSession)
fun KtClassOrObjectSymbol.translateToObjCClass(): ObjCClass? {
require(classKind == KtClassKind.CLASS)
if (!isVisibleInObjC()) return null
val superClass = getSuperClassSymbolNotAny()
val kotlinAnyName = "Base".getObjCKotlinStdlibClassOrProtocolName()
val superName = if (superClass == null) kotlinAnyName else throw RuntimeException("Super class translation isn't implemented yet")
val enumKind = this.classKind == KtClassKind.ENUM_CLASS
val final = if (this is KtSymbolWithModality) this.modality == Modality.FINAL else false
val attributes = if (enumKind || final) listOf(OBJC_SUBCLASSING_RESTRICTED) else emptyList()
val name = getObjCClassOrProtocolName()
val comment: ObjCComment? = annotationsList.toComment()
val origin: ObjCExportStubOrigin = getObjCStubOrigin()
val superProtocols: List<String> = superProtocols()
val members: List<ObjCExportStub> = members().flatMap { it.translateToObjCExportStubs() }
val categoryName: String? = null
val generics: List<ObjCGenericTypeDeclaration> = emptyList()
val superClassGenerics: List<ObjCNonNullReferenceType> = emptyList()
return ObjCInterfaceImpl(
name.objCName,
comment,
origin,
attributes,
superProtocols,
members,
categoryName,
generics,
superName.objCName,
superClassGenerics
)
}
private const val OBJC_SUBCLASSING_RESTRICTED = "objc_subclassing_restricted"
private fun abbreviate(name: String): String {
val normalizedName = name
.replaceFirstChar(Char::uppercaseChar)
.replace("-|\\.".toRegex(), "_")
val uppers = normalizedName.filterIndexed { index, character -> index == 0 || character.isUpperCase() }
if (uppers.length >= 3) return uppers
return normalizedName
}
/**
* Not implemented
*/
private fun KtAnnotationsList.toComment(): ObjCComment? {
return null
}
private val mustBeDocumentedAnnotationsStopList = setOf(
StandardNames.FqNames.deprecated,
KonanFqNames.objCName,
KonanFqNames.shouldRefineInSwift
)
@@ -0,0 +1,31 @@
package org.jetbrains.kotlin.objcexport
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.symbols.KtConstructorSymbol
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCInstanceType
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCMethod
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC
context(KtAnalysisSession, KtObjCExportSession)
fun KtConstructorSymbol.translateToObjCConstructors(): List<ObjCMethod> {
if (!isVisibleInObjC()) return emptyList()
val result = mutableListOf<ObjCMethod>()
result.add(buildObjCMethod())
result.add(
ObjCMethod(
null,
null,
false,
ObjCInstanceType,
listOf("new"),
emptyList(),
listOf(
"availability(swift, unavailable, message=\"use object initializers instead\")"
)
)
)
return result
}
@@ -0,0 +1,11 @@
@file:Suppress("UNUSED_PARAMETER")
package org.jetbrains.kotlin.objcexport
import org.jetbrains.kotlin.analysis.api.types.KtType
import org.jetbrains.kotlin.backend.konan.objcexport.BlockPointerBridge
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCReferenceType
internal fun KtType.translateToObjCFunctionType(typeBridge: BlockPointerBridge): ObjCReferenceType {
TODO()
}
@@ -7,10 +7,9 @@ package org.jetbrains.kotlin.objcexport
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.scopes.KtScope
import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.analysis.api.symbols.KtClassKind.CLASS
import org.jetbrains.kotlin.analysis.api.symbols.KtClassKind.INTERFACE
import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportStub
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCHeader
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCProtocol
@@ -18,7 +17,7 @@ import org.jetbrains.kotlin.backend.konan.objcexport.ObjCProtocol
context(KtAnalysisSession, KtObjCExportSession)
fun KtScope.translateToObjCHeader(): ObjCHeader {
val declarationsInScope = getAllSymbols()
.mapNotNull { symbol -> symbol.translateToObjCExportStubOrNull() }
.flatMap { symbol -> symbol.translateToObjCExportStubs() }
.toList()
return ObjCHeader(
@@ -34,10 +33,13 @@ fun KtScope.translateToObjCHeader(): ObjCHeader {
}
context(KtAnalysisSession, KtObjCExportSession)
internal fun KtSymbol.translateToObjCExportStubOrNull(): ObjCExportStub? {
internal fun KtSymbol.translateToObjCExportStubs(): List<ObjCExportStub> {
return when {
this is KtClassOrObjectSymbol && classKind == INTERFACE -> translateToObjCProtocol()
this is KtPropertySymbol -> translateToObjCProperty()
else -> null
this is KtClassOrObjectSymbol && classKind == INTERFACE -> listOfNotNull(translateToObjCProtocol())
this is KtClassOrObjectSymbol && classKind == CLASS -> listOfNotNull(translateToObjCClass())
this is KtConstructorSymbol -> translateToObjCConstructors()
this is KtPropertySymbol -> listOfNotNull(translateToObjCProperty())
this is KtFunctionSymbol -> listOfNotNull(translateToObjCMethod())
else -> emptyList()
}
}
}
@@ -0,0 +1,723 @@
@file:Suppress("UNUSED_PARAMETER")
package org.jetbrains.kotlin.objcexport
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.annotations.KtAnnotationApplicationWithArgumentsInfo
import org.jetbrains.kotlin.analysis.api.annotations.annotationInfos
import org.jetbrains.kotlin.analysis.api.annotations.annotations
import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.analysis.api.types.KtFunctionalType
import org.jetbrains.kotlin.analysis.api.types.KtType
import org.jetbrains.kotlin.backend.konan.InternalKotlinNativeApi
import org.jetbrains.kotlin.backend.konan.KonanFqNames
import org.jetbrains.kotlin.backend.konan.KonanPrimitiveType
import org.jetbrains.kotlin.backend.konan.cKeywords
import org.jetbrains.kotlin.backend.konan.objcexport.*
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.objcexport.Predefined.anyMethodSelectors
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC
internal val KtCallableSymbol.isConstructor: Boolean
get() = this is KtConstructorSymbol
internal val KtCallableSymbol.isArray: Boolean
get() = false //TODO: temp k2 workaround
context(KtAnalysisSession, KtObjCExportSession)
fun KtFunctionSymbol.translateToObjCMethod(): ObjCMethod? {
if (!isVisibleInObjC()) return null
if (anyMethodSelectors.containsKey(this.name)) return null //temp, find replacement for org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.isReal
return buildObjCMethod()
}
/**
* [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.buildMethod]
*/
context(KtAnalysisSession, KtObjCExportSession)
internal fun KtFunctionLikeSymbol.buildObjCMethod(unavailable: Boolean = false): ObjCMethod {
val baseMethodBridge = bridgeMethodImpl()
val origin: ObjCExportStubOrigin? = null
val returnType: ObjCType = mapReturnType(baseMethodBridge.returnBridge)
val parameters = collectParameters(baseMethodBridge, this)
val selector = getSelector()
val selectors: List<String> = splitSelector(selector)
val swiftName = getSwiftName()
val attributes = mutableListOf<String>()
val returnBridge = baseMethodBridge.returnBridge
val comment = buildComment(this, baseMethodBridge, parameters)
attributes += getSwiftPrivateAttribute() ?: swiftNameAttribute(swiftName)
if (returnBridge is MethodBridge.ReturnValue.WithError.ZeroForError && returnBridge.successMayBeZero) {
// Method may return zero on success, but
// standard Objective-C convention doesn't suppose this happening.
// Add non-standard convention hint for Swift:
attributes += "swift_error(nonnull_error)" // Means "failure <=> (error != nil)".
}
if (this.isConstructor && !isArray) { // TODO: check methodBridge instead.
attributes += "objc_designated_initializer"
}
if (unavailable) {
attributes += "unavailable"
} else {
/**
* Implement and use [org.jetbrains.kotlin.resolve.deprecation.DeprecationResolver]
*/
//attributes.addIfNotNull(getDeprecationAttribute(method))
}
return ObjCMethod(
comment,
origin,
true,
returnType,
selectors,
parameters,
attributes
)
}
context(KtAnalysisSession, KtObjCExportSession)
private fun collectParameters(baseMethodBridge: MethodBridge, method: KtFunctionLikeSymbol): List<ObjCParameter> {
fun unifyName(initialName: String, usedNames: Set<String>): String {
var unique = initialName.toValidObjCSwiftIdentifier()
while (unique in usedNames || unique in cKeywords) {
unique += "_"
}
return unique
}
val valueParametersAssociated = baseMethodBridge.valueParametersAssociated(method)
val parameters = mutableListOf<ObjCParameter>()
val usedNames = mutableSetOf<String>()
valueParametersAssociated.forEach { (bridge: MethodBridgeValueParameter, parameter: KtTypeParameterSymbol?) ->
val candidateName: String = when (bridge) {
is MethodBridgeValueParameter.Mapped -> {
if (parameter == null) throw IllegalStateException("Parameter shouldn't be null")
when {
method is KtPropertySetterSymbol -> "value"
else -> parameter.getObjCName().name(false)
}
}
MethodBridgeValueParameter.ErrorOutParameter -> "error"
is MethodBridgeValueParameter.SuspendCompletion -> "completionHandler"
}
val uniqueName = unifyName(candidateName, usedNames)
usedNames += uniqueName
val type = when (bridge) {
is MethodBridgeValueParameter.Mapped -> TODO("Fetch KtType from KtTypeParameterSymbol: $parameter")
MethodBridgeValueParameter.ErrorOutParameter ->
ObjCPointerType(ObjCNullableReferenceType(ObjCClassType("NSError")), nullable = true)
is MethodBridgeValueParameter.SuspendCompletion -> {
val resultType = if (bridge.useUnitCompletion) {
null
} else {
when (val it = method.returnType.translateToObjCReferenceType()) {
is ObjCNonNullReferenceType -> ObjCNullableReferenceType(it, isNullableResult = false)
is ObjCNullableReferenceType -> ObjCNullableReferenceType(it.nonNullType, isNullableResult = true)
}
}
ObjCBlockPointerType(
returnType = ObjCVoidType,
parameterTypes = listOfNotNull(
resultType,
ObjCNullableReferenceType(ObjCClassType("NSError"))
)
)
}
}
val origin: ObjCExportStubOrigin? = null
val todo: Nothing? = null
parameters += ObjCParameter(uniqueName, origin, type, todo)
}
return parameters
}
/**
* [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamerKt.toValidObjCSwiftIdentifier]
*/
internal fun String.toValidObjCSwiftIdentifier(): String {
if (this.isEmpty()) return "__"
return this.replace('$', '_') // TODO: handle more special characters.
.let { if (it.first().isDigit()) "_$it" else it }
.let { if (it == "_") "__" else it }
}
private val throwableClassId = ClassId.topLevel(StandardNames.FqNames.throwable)
/**
* [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.buildComment]
*/
private fun buildComment(method: KtFunctionLikeSymbol, bridge: MethodBridge, parameters: List<ObjCParameter>): ObjCComment? {
val visibility: Visibility
val isSuspend: Boolean
if (method is KtFunctionSymbol) {
isSuspend = method.isSuspend
visibility = method.visibility
} else if (method is KtConstructorSymbol) {
isSuspend = false
visibility = Visibilities.Public //check constructor
} else {
TODO("Unsupported type for comment building: $method")
}
val throwsComments = if (isSuspend || bridge.returnsError) {
val effectiveThrows = getEffectiveThrows(method).toSet()
when {
effectiveThrows.contains(throwableClassId) -> {
listOf("@note This method converts all Kotlin exceptions to errors.")
}
effectiveThrows.isNotEmpty() -> {
listOf(
buildString {
append("@note This method converts instances of ")
effectiveThrows.joinTo(this) { it.relativeClassName.asString() }
append(" to errors.")
},
"Other uncaught Kotlin exceptions are fatal."
)
}
else -> {
// Shouldn't happen though.
listOf("@warning All uncaught Kotlin exceptions are fatal.")
}
}
} else emptyList()
val visibilityComments = visibilityComments(visibility, "method")
val paramComments = method.valueParameters.flatMap { parameterDescriptor ->
parameters.find { parameter -> parameter.origin?.name == parameterDescriptor.name }?.let { parameter ->
mustBeDocumentedParamAttributeList(parameter, descriptor = parameterDescriptor)
} ?: emptyList()
}
val annotationsComments = mustBeDocumentedAttributeList(method.annotations)
val commentLines = annotationsComments + paramComments + throwsComments + visibilityComments
return if (commentLines.isNotEmpty()) ObjCComment(commentLines) else null
}
/**
* [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.mustBeDocumentedParamAttributeList]
*/
private fun mustBeDocumentedParamAttributeList(parameter: ObjCParameter, descriptor: KtValueParameterSymbol): List<String> {
descriptor.annotationsList.annotations
val mbdAnnotations = mustBeDocumentedAnnotations(descriptor.annotations).joinToString(" ")
return if (mbdAnnotations.isNotEmpty()) listOf("@param ${parameter.name} annotations $mbdAnnotations") else emptyList()
}
/**
* Not implemented [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.mustBeDocumentedAnnotations]
*/
private fun mustBeDocumentedAnnotations(annotations: List<KtAnnotationApplicationWithArgumentsInfo>): List<String> {
return emptyList()
}
private fun mustBeDocumentedAttributeList(annotations: List<KtAnnotationApplicationWithArgumentsInfo>): List<String> {
val mustBeDocumentedAnnotations = mustBeDocumentedAnnotations(annotations)
return if (mustBeDocumentedAnnotations.isNotEmpty()) {
listOf("@note annotations") + mustBeDocumentedAnnotations.map { " $it" }
} else emptyList()
}
/**
* [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.visibilityComments]
*/
private fun visibilityComments(visibility: Visibility, kind: String): List<String> {
return when (visibility) {
Visibilities.Protected -> listOf("@note This $kind has protected visibility in Kotlin source and is intended only for use by subclasses.")
else -> emptyList()
}
}
/**
* Not implemented [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.getEffectiveThrows]
*/
private fun getEffectiveThrows(method: KtFunctionLikeSymbol): Sequence<ClassId> {
return emptySequence()
}
internal fun KtCallableSymbol.getSwiftPrivateAttribute(): String? =
if (isRefinedInSwift()) "swift_private" else null
internal fun KtCallableSymbol.isRefinedInSwift(): Boolean = when {
// Note: the front-end checker requires all overridden descriptors to be either refined or not refined.
//overriddenDescriptors.isNotEmpty() -> overriddenDescriptors.first().isRefinedInSwift() //TODO: implement isRefinedInSwift
else -> annotationInfos.any { annotation ->
annotation.classId?.asSingleFqName() == KonanFqNames.refinesInSwift
}
}
context(KtAnalysisSession, KtObjCExportSession)
internal fun KtFunctionLikeSymbol.getSwiftName(): String {
//assert(mapper.isBaseMethod(method)) //TODO: implement isBaseMethod
getPredefined(this, Predefined.anyMethodSwiftNames)?.let { return it }
val parameters = bridgeMethodImpl().valueParametersAssociated(this)
val method = this
val sb = StringBuilder().apply {
append(getMangledName(forSwift = true))
append("(")
parameters@ for ((bridge, symbol) in parameters) {
val label = when (bridge) {
is MethodBridgeValueParameter.Mapped -> when {
//it is ReceiverParameterDescriptor -> it.getObjCName().asIdentifier(true) { "_" }
method is KtPropertySetterSymbol -> when (parameters.size) {
1 -> "_"
else -> "value"
}
else -> symbol!!.getObjCName().name(true)
}
MethodBridgeValueParameter.ErrorOutParameter -> continue@parameters
is MethodBridgeValueParameter.SuspendCompletion -> "completionHandler"
}
append(label)
append(":")
}
append(")")
}
return sb.toString()
}
internal object Predefined {
val anyMethodSelectors = mapOf(
"hashCode" to "hash",
"toString" to "description",
"equals" to "isEqual:"
).mapKeys { Name.identifier(it.key) }
val anyMethodSwiftNames = mapOf(
"hashCode" to "hash()",
"toString" to "description()",
"equals" to "isEqual(_:)"
).mapKeys { Name.identifier(it.key) }
}
/**
* [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.splitSelector]
*/
private fun splitSelector(selector: String): List<String> {
return if (!selector.endsWith(":")) {
listOf(selector)
} else {
selector.trimEnd(':').split(':').map { "$it:" }
}
}
/**
* Not implemented [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamerImpl.getPredefined]
*/
private fun <T : Any> getPredefined(method: KtFunctionLikeSymbol, predefinedForAny: Map<Name, T>): T? {
return null
}
/**
* [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamerImpl.getSelector]
*/
context(KtAnalysisSession, KtObjCExportSession)
fun KtFunctionLikeSymbol.getSelector(): String {
getPredefined(this, Predefined.anyMethodSelectors)?.let { return it }
val parameters = bridgeMethodImpl().valueParametersAssociated(this)
val method = this
val sb = StringBuilder()
sb.append(method.getMangledName(forSwift = false))
parameters.forEachIndexed { index, (bridge, typeParameterSymbol) ->
val name = when (bridge) {
is MethodBridgeValueParameter.Mapped -> when {
method is KtPropertySetterSymbol -> when (parameters.size) {
1 -> ""
else -> "value"
}
else -> typeParameterSymbol!!.getObjCName().name(false)
}
MethodBridgeValueParameter.ErrorOutParameter -> "error"
is MethodBridgeValueParameter.SuspendCompletion -> "completionHandler"
}
if (index == 0) {
sb.append(
when {
bridge is MethodBridgeValueParameter.ErrorOutParameter -> "AndReturn"
bridge is MethodBridgeValueParameter.SuspendCompletion -> "With"
method.isConstructor -> "With"
else -> ""
}
)
sb.append(name.replaceFirstChar(Char::uppercaseChar))
} else {
sb.append(name)
}
sb.append(':')
}
return sb.toString()
}
/**
* Not implemented
*/
private fun KtTypeParameterSymbol.getObjCName(): ObjCExportName {
TODO()
}
///**
// * [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamerKt.toIdentifier]
// */
//private fun String.toIdentifier(): String = this.toValidObjCSwiftIdentifier()
///**
// * [org.jetbrains.kotlin.backend.konan.objcexport.ObjCName]
// */
//private class ObjCName(
// private val kotlinName: String,
// private val objCName: String?,
// private val swiftName: String?,
// val isExact: Boolean,
//) {
// // TODO: Prevent mangling when objCName or swiftName is provided
//
// fun asString(forSwift: Boolean): String = swiftName.takeIf { forSwift } ?: objCName ?: kotlinName
//
// fun asIdentifier(forSwift: Boolean, default: (String) -> String = { it.toIdentifier() }): String =
// swiftName.takeIf { forSwift } ?: objCName ?: default(kotlinName)
//}
context(KtAnalysisSession, KtObjCExportSession)
private fun KtFunctionLikeSymbol.getMangledName(forSwift: Boolean): String {
if (this.isConstructor) {
return if (isArray && !forSwift) "array" else "init"
}
val candidate = when (this) {
is KtPropertyGetterSymbol -> {
this.getObjCFunctionName().name(forSwift)
}
is KtPropertySetterSymbol -> {
this.getObjCFunctionName().name(forSwift)
//TODO: find replacement for [this.correspondingProperty]
// "set${
// this.correspondingProperty.getObjCName().asString(forSwift).replaceFirstChar(kotlin.Char::uppercaseChar)
// }".toIdentifier()
}
else -> {
this.getObjCFunctionName().name(forSwift)
}
}
return candidate.mangleIfSpecialFamily("do")
}
/**
* [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamerImpl.mangleIfSpecialFamily]
*/
private fun String.mangleIfSpecialFamily(prefix: String): String {
val trimmed = this.dropWhile { it == '_' }
for (family in listOf("alloc", "copy", "mutableCopy", "new", "init")) {
if (trimmed.startsWithWords(family)) {
// Then method can be detected as having special family by Objective-C compiler.
// mangle the name:
return prefix + this.replaceFirstChar(Char::uppercaseChar)
}
}
// TODO: handle clashes with NSObject methods etc.
return this
}
/**
* [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamerImpl.startsWithWords]
*/
private fun String.startsWithWords(words: String) = this.startsWith(words) &&
(this.length == words.length || !this[words.length].isLowerCase())
@InternalKotlinNativeApi
fun MethodBridge.valueParametersAssociated(
function: KtFunctionLikeSymbol,
): List<Pair<MethodBridgeValueParameter, KtTypeParameterSymbol?>> {
val kotlinParameters = function.typeParameters.iterator()
if (!kotlinParameters.hasNext()) return emptyList()
val skipFirstKotlinParameter = when (this.receiver) {
MethodBridgeReceiver.Static -> false
MethodBridgeReceiver.Factory, MethodBridgeReceiver.Instance -> true
}
if (skipFirstKotlinParameter) {
kotlinParameters.next()
}
return this.valueParameters.map {
when (it) {
is MethodBridgeValueParameter.Mapped -> it to kotlinParameters.next()
is MethodBridgeValueParameter.SuspendCompletion,
is MethodBridgeValueParameter.ErrorOutParameter,
-> it to null
}
}.also { assert(!kotlinParameters.hasNext()) }
}
/**
* [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.mapReturnType]
*/
context(KtAnalysisSession, KtObjCExportSession)
fun KtFunctionLikeSymbol.mapReturnType(returnBridge: MethodBridge.ReturnValue): ObjCType {
return when (returnBridge) {
MethodBridge.ReturnValue.Suspend,
MethodBridge.ReturnValue.Void,
-> ObjCVoidType
MethodBridge.ReturnValue.HashCode -> ObjCPrimitiveType.NSUInteger
is MethodBridge.ReturnValue.Mapped -> returnType.mapType(returnBridge.bridge)
MethodBridge.ReturnValue.WithError.Success -> ObjCPrimitiveType.BOOL
is MethodBridge.ReturnValue.WithError.ZeroForError -> {
val successReturnType = mapReturnType(returnBridge.successBridge)
if (!returnBridge.successMayBeZero) {
check(
successReturnType is ObjCNonNullReferenceType
|| (successReturnType is ObjCPointerType && !successReturnType.nullable)
) {
"Unexpected return type: $successReturnType in $this"
}
}
successReturnType.makeNullableIfReferenceOrPointer()
}
MethodBridge.ReturnValue.Instance.InitResult,
MethodBridge.ReturnValue.Instance.FactoryResult,
-> ObjCInstanceType
}
}
/**
* [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportMapperKt.bridgeMethodImpl]
*/
context(KtAnalysisSession, KtObjCExportSession)
internal fun KtFunctionLikeSymbol.bridgeMethodImpl(): MethodBridge {
val isTopLevel = false //TODO implement and use ObjCExportMapper.isTopLevel
val valueParameters = mutableListOf<MethodBridgeValueParameter>()
val receiver = if (isConstructor && isArray) {
MethodBridgeReceiver.Factory
} else if (isTopLevel) {
MethodBridgeReceiver.Static
} else {
MethodBridgeReceiver.Instance
}
this.valueParameters.forEach {
valueParameters += bridgeParameter(it.returnType)
}
if (this is KtFunctionSymbol && isSuspend) {
valueParameters += MethodBridgeValueParameter.SuspendCompletion(false)
}
return MethodBridge(
bridgeReturnType(),
receiver,
valueParameters
)
}
/**
* [ObjCExportMapper.bridgeParameter]
*/
context(KtAnalysisSession, KtObjCExportSession)
private fun bridgeParameter(type: KtType): MethodBridgeValueParameter {
return MethodBridgeValueParameter.Mapped(bridgeType(type))
}
/**
* [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.mapType]
*/
context(KtAnalysisSession, KtObjCExportSession)
private fun KtType.mapType(typeBridge: TypeBridge): ObjCType {
//if (!this.isObjCObjectType()) return null //TODO implement isObjCObjectType
return when (typeBridge) {
is ReferenceBridge -> this.translateToObjCReferenceType()
is BlockPointerBridge -> this.translateToObjCFunctionType(typeBridge)
is ValueTypeBridge -> {
when {
isBoolean -> ObjCPrimitiveType.BOOL
isChar -> ObjCPrimitiveType.int8_t
isByte -> ObjCPrimitiveType.char
isShort -> ObjCPrimitiveType.int16_t
isInt -> ObjCPrimitiveType.int32_t
isLong -> ObjCPrimitiveType.long_long
isFloat -> ObjCPrimitiveType.float
isDouble -> ObjCPrimitiveType.double
isUByte -> ObjCPrimitiveType.unsigned_char
isUShort -> ObjCPrimitiveType.unsigned_short
isUInt -> ObjCPrimitiveType.unsigned_int
isULong -> ObjCPrimitiveType.unsigned_long_long
else -> TODO("Handle primitive KtType mapping to ObjCType: $this")
}
}
}
}
/**
* [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportMapperKt.isReferenceOrPointer]
*/
private fun TypeBridge.isReferenceOrPointer(): Boolean = when (this) {
ReferenceBridge, is BlockPointerBridge -> true
is ValueTypeBridge -> this.objCValueType == ObjCValueType.POINTER
}
/**
* [ObjCExportMapper.bridgeFunctionType]
*/
context(KtAnalysisSession)
private fun bridgeFunctionType(type: KtType): TypeBridge {
val numberOfParameters: Int
val returnType: KtType
if (type is KtFunctionalType) {
numberOfParameters = type.parameterTypes.size
returnType = type.parameterTypes.last()
} else {
numberOfParameters = 0
returnType = type
}
val returnsVoid = returnType.isUnit || returnType.isNothing
return BlockPointerBridge(numberOfParameters, returnsVoid)
}
/**
* [ObjCExportMapper.bridgeType]
*/
context(KtAnalysisSession)
private fun bridgeType(
type: KtType,
): TypeBridge {
return if (type.isPrimitive) {
val objCType = when {
type.isBoolean -> ObjCValueType.BOOL
type.isChar -> ObjCValueType.UNICHAR
type.isByte -> ObjCValueType.CHAR
type.isShort -> ObjCValueType.SHORT
type.isInt -> ObjCValueType.INT
type.isLong -> ObjCValueType.LONG_LONG
type.isFloat -> ObjCValueType.FLOAT
type.isDouble -> ObjCValueType.DOUBLE
type.isUByte -> ObjCValueType.UNSIGNED_CHAR
type.isUShort -> ObjCValueType.UNSIGNED_SHORT
type.isUInt -> ObjCValueType.UNSIGNED_INT
type.isULong -> ObjCValueType.UNSIGNED_LONG_LONG
else ->
/**
* Handle [KonanPrimitiveType.NON_NULL_NATIVE_PTR] and [KonanPrimitiveType.VECTOR128]
*/
TODO()
}
ValueTypeBridge(objCType)
} else if (type.isFunctionType) {
bridgeFunctionType(type)
} else {
ReferenceBridge
}
}
/**
* [ObjCExportMapper.bridgeReturnType]
*/
context(KtAnalysisSession, KtObjCExportSession)
internal fun KtCallableSymbol.bridgeReturnType(): MethodBridge.ReturnValue {
val convertExceptionsToErrors = false // TODO: Add exception handling and return MethodBridge.ReturnValue.WithError.ZeroForError
if (isArray) {
return MethodBridge.ReturnValue.Instance.FactoryResult
} else if (isConstructor) {
val result = MethodBridge.ReturnValue.Instance.InitResult
if (convertExceptionsToErrors) {
MethodBridge.ReturnValue.WithError.ZeroForError(result, successMayBeZero = false)
} else {
return result
}
} else if (returnType.isSuspendFunctionType) {
return MethodBridge.ReturnValue.Suspend
}
//TODO: handle hashCode
// descriptor.containingDeclaration.let { it is ClassDescriptor && KotlinBuiltIns.isAny(it) } &&
// descriptor.name.asString() == "hashCode" -> {
// assert(!convertExceptionsToErrors)
// MethodBridge.ReturnValue.HashCode
// }
//TODO: handle getter
// descriptor is PropertyGetterDescriptor -> {
// assert(!convertExceptionsToErrors)
// MethodBridge.ReturnValue.Mapped(bridgePropertyType(descriptor.correspondingProperty))
// }
if (returnType.isUnit || returnType.isNothing) {
return if (convertExceptionsToErrors) {
MethodBridge.ReturnValue.WithError.Success
} else {
MethodBridge.ReturnValue.Void
}
}
val returnTypeBridge = bridgeType(returnType)
val successReturnValueBridge = MethodBridge.ReturnValue.Mapped(returnTypeBridge)
return if (convertExceptionsToErrors) {
val canReturnZero = !returnTypeBridge.isReferenceOrPointer() || returnType.canBeNull
MethodBridge.ReturnValue.WithError.ZeroForError(
successReturnValueBridge,
successMayBeZero = canReturnZero
)
} else {
successReturnValueBridge
}
}
@@ -8,11 +8,58 @@ package org.jetbrains.kotlin.objcexport
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySymbol
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCProperty
import org.jetbrains.kotlin.backend.konan.objcexport.swiftNameAttribute
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC
context(KtAnalysisSession, KtObjCExportSession)
fun KtPropertySymbol.translateToObjCProperty(): ObjCProperty? {
if (!isVisibleInObjC()) return null
// TODO
return null
return buildProperty()
}
/**
* [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.buildProperty]
*/
context(KtAnalysisSession, KtObjCExportSession)
fun KtPropertySymbol.buildProperty(): ObjCProperty {
val propertyName = getObjCPropertyName()
val name = propertyName.objCName
val getterBridge = getter?.bridgeMethodImpl()
val type = getter?.mapReturnType(getterBridge!!.returnBridge)
val attributes = mutableListOf<String>()
val setterName: String?
if (!getterBridge!!.isInstance) {
attributes += "class"
}
val propertySetter = setter
// Note: the condition below is similar to "toObjCMethods" logic in [ObjCExportedInterface.createCodeSpec].
val shouldBeSetterExposed = true //TODO: mapper.shouldBeExposed
if (propertySetter != null && shouldBeSetterExposed) {
val setterSelector = propertySetter.getSelector()
setterName = if (setterSelector != "set" + name.replaceFirstChar(Char::uppercaseChar) + ":") setterSelector else null
} else {
attributes += "readonly"
setterName = null
}
val getterName = null //TODO: Fix and use getter.getSelector(), it should return name when it's available
val declarationAttributes = mutableListOf(getSwiftPrivateAttribute() ?: swiftNameAttribute(propertyName.swiftName))
//TODO: implement and use [org.jetbrains.kotlin.resolve.deprecation.DeprecationResolver]
//declarationAttributes.addIfNotNull(mapper.getDeprecation(property)?.toDeprecationAttribute())
return ObjCProperty(
name,
null,
null,
type!!,
attributes,
setterName,
getterName,
declarationAttributes
)
}
@@ -6,7 +6,6 @@
package org.jetbrains.kotlin.objcexport
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtClassKind
import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol
import org.jetbrains.kotlin.analysis.api.types.KtClassType
@@ -16,32 +15,18 @@ import org.jetbrains.kotlin.backend.konan.objcexport.ObjCProtocol
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCProtocolImpl
import org.jetbrains.kotlin.backend.konan.objcexport.toNameAttributes
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC
import org.jetbrains.kotlin.objcexport.analysisApiUtils.members
context(KtAnalysisSession, KtObjCExportSession)
fun KtClassOrObjectSymbol.translateToObjCProtocol(): ObjCProtocol? {
// TODO: check if this symbol shall be exposed in the first place
require(classKind == KtClassKind.INTERFACE)
if (isVisibleInObjC()) return null
if (!isVisibleInObjC()) return null
// TODO: Check error type!
val name = getObjCClassOrProtocolName()
val members = getMemberScope().getAllSymbols()
.filterIsInstance<KtCallableSymbol>()
.filter { memberSymbol -> memberSymbol.isVisibleInObjC() }
.mapNotNull { memberSymbol -> memberSymbol.translateToObjCExportStubOrNull() }
.toList()
val superProtocols = superTypes
.asSequence()
.filter { type -> !type.isAny }
.mapNotNull { type -> type as? KtClassType }
.flatMap { type -> type.qualifiers }
.mapNotNull { qualifier -> qualifier as? KtClassTypeQualifier.KtResolvedClassTypeQualifier }
.mapNotNull { it.symbol as? KtClassOrObjectSymbol }
.filter { superInterface -> superInterface.classKind == KtClassKind.INTERFACE }
.map { superInterface -> superInterface.getObjCClassOrProtocolName().objCName }
.toList()
val members = members().flatMap { it.translateToObjCExportStubs() }
// TODO: Resolve comment
val comment: ObjCComment? = null
@@ -51,7 +36,21 @@ fun KtClassOrObjectSymbol.translateToObjCProtocol(): ObjCProtocol? {
comment = comment,
origin = getObjCStubOrigin(),
attributes = name.toNameAttributes(),
superProtocols = superProtocols,
superProtocols = superProtocols(),
members = members
)
}
context(KtAnalysisSession, KtObjCExportSession)
internal fun KtClassOrObjectSymbol.superProtocols(): List<String> {
return superTypes
.asSequence()
.filter { type -> !type.isAny }
.mapNotNull { type -> type as? KtClassType }
.flatMap { type -> type.qualifiers }
.mapNotNull { qualifier -> qualifier as? KtClassTypeQualifier.KtResolvedClassTypeQualifier }
.mapNotNull { it.symbol as? KtClassOrObjectSymbol }
.filter { superInterface -> superInterface.classKind == KtClassKind.INTERFACE }
.map { superInterface -> superInterface.getObjCClassOrProtocolName().objCName }
.toList()
}
@@ -0,0 +1,55 @@
package org.jetbrains.kotlin.objcexport
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.types.KtType
import org.jetbrains.kotlin.backend.konan.objcexport.*
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.name.ClassId
/**
* [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.mapReferenceType]
*/
context(KtAnalysisSession, KtObjCExportSession)
internal fun KtType.translateToObjCReferenceType(): ObjCReferenceType {
return mapToReferenceTypeIgnoringNullability().withNullabilityOf(this)
}
/**
* [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.mapReferenceTypeIgnoringNullability]
*/
context(KtAnalysisSession, KtObjCExportSession)
private fun KtType.mapToReferenceTypeIgnoringNullability(): ObjCNonNullReferenceType {
/**
* Simplified version of [org.jetbrains.kotlin.backend.konan.objcexport.CustomTypeMapper]
*/
val typesMap = mutableMapOf<ClassId, String>().apply {
this[ClassId.topLevel(StandardNames.FqNames.list)] = "NSArray"
this[ClassId.topLevel(StandardNames.FqNames.mutableList)] = "NSMutableArray"
this[ClassId.topLevel(StandardNames.FqNames.set)] = "NSSet"
this[ClassId.topLevel(StandardNames.FqNames.mutableSet)] = "MutableSet".getObjCKotlinStdlibClassOrProtocolName().objCName
this[ClassId.topLevel(StandardNames.FqNames.map)] = "NSDictionary"
this[ClassId.topLevel(StandardNames.FqNames.mutableMap)] = "MutableDictionary".getObjCKotlinStdlibClassOrProtocolName().objCName
this[ClassId.topLevel(StandardNames.FqNames.string.toSafe())] = "NSString"
}
NSNumberKind.entries.forEach {
val classId = it.mappedKotlinClassId
if (classId != null) {
typesMap[classId] = classId.shortClassName.asString().getObjCKotlinStdlibClassOrProtocolName().objCName
}
}
val typeName = typesMap[this.expandedClassSymbol?.classIdIfNonLocal]
?: throw IllegalStateException("Unsupported mapping type for $this")
return ObjCClassType(typeName)
}
private fun ObjCNonNullReferenceType.withNullabilityOf(kotlinType: KtType): ObjCReferenceType {
return if (kotlinType.nullability.isNullable) {
ObjCNullableReferenceType(this)
} else {
this
}
}