[ObjCExport] Add initial top level function export

KT-64874
This commit is contained in:
eugene.levenetc
2024-01-10 17:43:09 +01:00
committed by Space Team
parent e67042118a
commit a778c6d9aa
16 changed files with 481 additions and 299 deletions
@@ -0,0 +1,17 @@
package org.jetbrains.kotlin.objcexport.analysisApiUtils
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportClassOrProtocolName
import org.jetbrains.kotlin.objcexport.KtObjCExportSession
import org.jetbrains.kotlin.objcexport.getObjCKotlinStdlibClassOrProtocolName
/**
* Some entities like top level functions are wrapped into classes with Base super class.
*
* @interface FooKt : Base
* + (NSString *)myTopLevelFunction __attribute__((swift_name("myTopLevelFunction()")));
* @end
*/
context(KtObjCExportSession)
internal fun getDefaultSuperClassOrProtocolName(): ObjCExportClassOrProtocolName {
return "Base".getObjCKotlinStdlibClassOrProtocolName()
}
@@ -0,0 +1,172 @@
package org.jetbrains.kotlin.objcexport.analysisApiUtils
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionLikeSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionSymbol
import org.jetbrains.kotlin.analysis.api.types.KtFunctionalType
import org.jetbrains.kotlin.analysis.api.types.KtType
import org.jetbrains.kotlin.backend.konan.KonanPrimitiveType
import org.jetbrains.kotlin.backend.konan.objcexport.*
import org.jetbrains.kotlin.objcexport.*
/**
* [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportMapperKt.bridgeMethodImpl]
*/
context(KtAnalysisSession, KtObjCExportSession)
internal fun KtFunctionLikeSymbol.getMethodBridge(): MethodBridge {
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))
}
/**
* [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.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.bridgeReturnType]
*/
context(KtAnalysisSession, KtObjCExportSession)
private 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
}
}
/**
* [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
}
@@ -0,0 +1,6 @@
package org.jetbrains.kotlin.objcexport.analysisApiUtils
import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol
internal val KtCallableSymbol.isTopLevel: Boolean
get() = callableIdIfNonLocal?.classId == null
@@ -2,9 +2,8 @@ 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)
context(KtAnalysisSession)
internal fun KtClassOrObjectSymbol.members(): List<KtSymbol> {
return getMemberScope()
.getAllSymbols()
@@ -0,0 +1,3 @@
package org.jetbrains.kotlin.objcexport
internal const val OBJC_SUBCLASSING_RESTRICTED = "objc_subclassing_restricted"
@@ -0,0 +1,12 @@
package org.jetbrains.kotlin.objcexport
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportFileName
context(KtObjCExportSession)
internal fun String.getObjCFileName(): ObjCExportFileName {
return ObjCExportFileName(
swiftName = this,
objCName = "${configuration.frameworkName.orEmpty()}$this"
)
}
@@ -9,6 +9,7 @@ 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.getDefaultSuperClassOrProtocolName
import org.jetbrains.kotlin.objcexport.analysisApiUtils.getSuperClassSymbolNotAny
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC
import org.jetbrains.kotlin.objcexport.analysisApiUtils.members
@@ -19,7 +20,7 @@ fun KtClassOrObjectSymbol.translateToObjCClass(): ObjCClass? {
if (!isVisibleInObjC()) return null
val superClass = getSuperClassSymbolNotAny()
val kotlinAnyName = "Base".getObjCKotlinStdlibClassOrProtocolName()
val kotlinAnyName = getDefaultSuperClassOrProtocolName()
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
@@ -48,8 +49,6 @@ fun KtClassOrObjectSymbol.translateToObjCClass(): ObjCClass? {
)
}
private const val OBJC_SUBCLASSING_RESTRICTED = "objc_subclassing_restricted"
private fun abbreviate(name: String): String {
val normalizedName = name
.replaceFirstChar(Char::uppercaseChar)
@@ -72,4 +71,4 @@ private val mustBeDocumentedAnnotationsStopList = setOf(
StandardNames.FqNames.deprecated,
KonanFqNames.objCName,
KonanFqNames.shouldRefineInSwift
)
)
@@ -6,24 +6,23 @@
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.backend.konan.objcexport.ObjCExportStub
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCHeader
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCProtocol
import org.jetbrains.kotlin.psi.KtFile
context(KtAnalysisSession, KtObjCExportSession)
fun KtScope.translateToObjCHeader(): ObjCHeader {
val declarationsInScope = getAllSymbols()
.flatMap { symbol -> symbol.translateToObjCExportStubs() }
.toList()
fun translateToObjCHeader(files: List<KtFile>) : ObjCHeader {
val declarations = files.flatMap { ktFile -> ktFile.translateToObjCExportStubs() }
return ObjCHeader(
stubs = declarationsInScope,
stubs = declarations,
classForwardDeclarations = emptySet(),
protocolForwardDeclarations = declarationsInScope
protocolForwardDeclarations = declarations
.filterIsInstance<ObjCProtocol>()
.flatMap { it.superProtocols }
.toSet(),
@@ -32,9 +31,22 @@ fun KtScope.translateToObjCHeader(): ObjCHeader {
)
}
context(KtAnalysisSession, KtObjCExportSession)
fun KtFile.translateToObjCExportStubs(): List<ObjCExportStub> {
return this.getFileSymbol().translateToObjCExportStubs()
}
context(KtAnalysisSession, KtObjCExportSession)
fun KtFileSymbol.translateToObjCExportStubs(): List<ObjCExportStub> {
return listOfNotNull(translateToObjCTopLevelInterfaceFileFacade()) + getFileScope().getClassifierSymbols()
.flatMap { classifierSymbol -> classifierSymbol.translateToObjCExportStubs() }
}
context(KtAnalysisSession, KtObjCExportSession)
internal fun KtSymbol.translateToObjCExportStubs(): List<ObjCExportStub> {
return when {
this is KtFileSymbol -> translateToObjCExportStubs()
this is KtClassOrObjectSymbol && classKind == INTERFACE -> listOfNotNull(translateToObjCProtocol())
this is KtClassOrObjectSymbol && classKind == CLASS -> listOfNotNull(translateToObjCClass())
this is KtConstructorSymbol -> translateToObjCConstructors()
@@ -2,25 +2,26 @@
package org.jetbrains.kotlin.objcexport
import com.intellij.openapi.util.io.FileUtil
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.name.NameUtils
import org.jetbrains.kotlin.objcexport.Predefined.anyMethodSelectors
import org.jetbrains.kotlin.objcexport.analysisApiUtils.getMethodBridge
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC
import org.jetbrains.kotlin.psi.KtFile
internal val KtCallableSymbol.isConstructor: Boolean
get() = this is KtConstructorSymbol
@@ -29,27 +30,39 @@ internal val KtCallableSymbol.isArray: Boolean
get() = false //TODO: temp k2 workaround
context(KtAnalysisSession, KtObjCExportSession)
fun KtFunctionSymbol.translateToObjCMethod(): ObjCMethod? {
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()
}
context(KtAnalysisSession, KtObjCExportSession)
fun KtFileSymbol.getObjCFileClassOrProtocolName(): ObjCExportFileName? {
val ktFile = this.psi as? KtFile ?: return null
val name = NameUtils.getPackagePartClassNamePrefix(FileUtil.getNameWithoutExtension(ktFile.name)) + "Kt"
return name.toIdentifier().getObjCFileName()
}
/**
* [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.buildMethod]
*/
context(KtAnalysisSession, KtObjCExportSession)
internal fun KtFunctionLikeSymbol.buildObjCMethod(unavailable: Boolean = false): ObjCMethod {
val baseMethodBridge = bridgeMethodImpl()
internal fun KtFunctionLikeSymbol.buildObjCMethod(
unavailable: Boolean = false,
): ObjCMethod {
val bridge = getMethodBridge()
val origin: ObjCExportStubOrigin? = null
val returnType: ObjCType = mapReturnType(baseMethodBridge.returnBridge)
val parameters = collectParameters(baseMethodBridge, this)
val selector = getSelector()
val returnType: ObjCType = mapReturnType(bridge.returnBridge)
val parameters = translateToObjCParameters(bridge)
val selector = getSelector(bridge)
val selectors: List<String> = splitSelector(selector)
val swiftName = getSwiftName()
val swiftName = getSwiftName(bridge)
val attributes = mutableListOf<String>()
val returnBridge = baseMethodBridge.returnBridge
val comment = buildComment(this, baseMethodBridge, parameters)
val returnBridge = bridge.returnBridge
val comment = buildComment(this, bridge, parameters)
attributes += getSwiftPrivateAttribute() ?: swiftNameAttribute(swiftName)
@@ -74,78 +87,16 @@ internal fun KtFunctionLikeSymbol.buildObjCMethod(unavailable: Boolean = false):
}
return ObjCMethod(
comment,
origin,
true,
returnType,
selectors,
parameters,
attributes
comment = comment,
origin = origin,
isInstanceMethod = bridge.isInstance || isConstructor,
returnType = returnType,
selectors = selectors,
parameters = parameters,
attributes = 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]
@@ -269,12 +220,12 @@ internal fun KtCallableSymbol.isRefinedInSwift(): Boolean = when {
}
context(KtAnalysisSession, KtObjCExportSession)
internal fun KtFunctionLikeSymbol.getSwiftName(): String {
internal fun KtFunctionLikeSymbol.getSwiftName(methodBridge: MethodBridge): String {
//assert(mapper.isBaseMethod(method)) //TODO: implement isBaseMethod
getPredefined(this, Predefined.anyMethodSwiftNames)?.let { return it }
val parameters = bridgeMethodImpl().valueParametersAssociated(this)
val parameters = methodBridge.valueParametersAssociated(this)
val method = this
val sb = StringBuilder().apply {
@@ -343,11 +294,11 @@ private fun <T : Any> getPredefined(method: KtFunctionLikeSymbol, predefinedForA
* [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamerImpl.getSelector]
*/
context(KtAnalysisSession, KtObjCExportSession)
fun KtFunctionLikeSymbol.getSelector(): String {
fun KtFunctionLikeSymbol.getSelector(methodBridge: MethodBridge): String {
getPredefined(this, Predefined.anyMethodSelectors)?.let { return it }
val parameters = bridgeMethodImpl().valueParametersAssociated(this)
val parameters = methodBridge.valueParametersAssociated(this)
val method = this
@@ -389,35 +340,6 @@ fun KtFunctionLikeSymbol.getSelector(): String {
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 {
@@ -466,7 +388,7 @@ private fun String.mangleIfSpecialFamily(prefix: String): String {
* [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())
(this.length == words.length || !this[words.length].isLowerCase())
@InternalKotlinNativeApi
fun MethodBridge.valueParametersAssociated(
@@ -513,7 +435,7 @@ fun KtFunctionLikeSymbol.mapReturnType(returnBridge: MethodBridge.ReturnValue):
if (!returnBridge.successMayBeZero) {
check(
successReturnType is ObjCNonNullReferenceType
|| (successReturnType is ObjCPointerType && !successReturnType.nullable)
|| (successReturnType is ObjCPointerType && !successReturnType.nullable)
) {
"Unexpected return type: $successReturnType in $this"
}
@@ -528,44 +450,6 @@ fun KtFunctionLikeSymbol.mapReturnType(returnBridge: MethodBridge.ReturnValue):
}
}
/**
* [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]
@@ -599,125 +483,3 @@ private fun KtType.mapType(typeBridge: TypeBridge): ObjCType {
}
}
}
/**
* [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
}
}
@@ -0,0 +1,78 @@
package org.jetbrains.kotlin.objcexport
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionLikeSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySetterSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtTypeParameterSymbol
import org.jetbrains.kotlin.backend.konan.cKeywords
import org.jetbrains.kotlin.backend.konan.objcexport.*
context(KtAnalysisSession, KtObjCExportSession)
internal fun KtFunctionLikeSymbol.translateToObjCParameters(baseMethodBridge: MethodBridge): 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(this)
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 {
this 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 = this.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
}
/**
* Not implemented
*/
internal fun KtTypeParameterSymbol.getObjCName(): ObjCExportName {
TODO()
}
@@ -9,6 +9,7 @@ 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.getMethodBridge
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC
context(KtAnalysisSession, KtObjCExportSession)
@@ -24,12 +25,12 @@ context(KtAnalysisSession, KtObjCExportSession)
fun KtPropertySymbol.buildProperty(): ObjCProperty {
val propertyName = getObjCPropertyName()
val name = propertyName.objCName
val getterBridge = getter?.bridgeMethodImpl()
val getterBridge = getter?.getMethodBridge()
val type = getter?.mapReturnType(getterBridge!!.returnBridge)
val attributes = mutableListOf<String>()
val setterName: String?
if (!getterBridge!!.isInstance) {
if (getterBridge!!.isInstance) {
attributes += "class"
}
@@ -38,7 +39,7 @@ fun KtPropertySymbol.buildProperty(): ObjCProperty {
val shouldBeSetterExposed = true //TODO: mapper.shouldBeExposed
if (propertySetter != null && shouldBeSetterExposed) {
val setterSelector = propertySetter.getSelector()
val setterSelector = propertySetter.getSelector(getterBridge)
setterName = if (setterSelector != "set" + name.replaceFirstChar(Char::uppercaseChar) + ":") setterSelector else null
} else {
attributes += "readonly"
@@ -0,0 +1,72 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.objcexport
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.symbols.KtFileSymbol
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCInterface
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCInterfaceImpl
import org.jetbrains.kotlin.objcexport.analysisApiUtils.getDefaultSuperClassOrProtocolName
/**
* Translates top level functions/properties inside the given [this] file as a single [ObjCInterface].
* ## example:
* given a file "Foo.kt"
*
* ```kotlin
*
* fun myTopLevelFunction() = 42
*
* val myTopLevelProperty get() = 42
*
* class Bar {
* fun bar() = Unit
* }
*
* ```
*
* This will be exporting two Interfaces:
*
* ```
* FooKt: Base
* - myTopLevelFunction
* - myTopLevelProperty
*
* Bar
* - bar
* ```
*
* Where `FooKt` would be the "top level interface file facade" returned by this function.
*/
context(KtAnalysisSession, KtObjCExportSession)
fun KtFileSymbol.translateToObjCTopLevelInterfaceFileFacade(): ObjCInterface? {
val topLevelCallableStubs = getFileScope().getCallableSymbols()
.flatMap { callableSymbol -> callableSymbol.translateToObjCExportStubs() }
.toList()
/* If there are no top level functions or properties, we do not need to export a file facade */
.ifEmpty { return null }
val fileName = getObjCFileClassOrProtocolName()
?: throw IllegalStateException("Top level file '$this' cannot be translated without file name")
val name = fileName.objCName
val attributes = listOf(OBJC_SUBCLASSING_RESTRICTED)
val superClass = getDefaultSuperClassOrProtocolName()
return ObjCInterfaceImpl(
name = name,
comment = null,
origin = null,
attributes = attributes,
superProtocols = emptyList(),
members = topLevelCallableStubs,
categoryName = null,
generics = emptyList(),
superClass = superClass.objCName,
superClassGenerics = emptyList()
)
}
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.objcexport.testUtils
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.scopes.KtScope
import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtNamedClassOrObjectSymbol
import org.jetbrains.kotlin.name.Name
@@ -43,3 +44,7 @@ fun KtScope.getFunctionOrFail(name: String): KtFunctionSymbol {
return symbol
}
context(KtAnalysisSession)
fun KtClassOrObjectSymbol.getFunctionOrFail(name: String) : KtFunctionSymbol {
return this.getMemberScope().getFunctionOrFail(name)
}
@@ -31,10 +31,8 @@ object AnalysisApiHeaderGenerator : HeaderGenerator {
val session = createStandaloneAnalysisApiSession(root.listFiles().orEmpty().filter { it.extension == "kt" })
val (module, files) = session.modulesWithFiles.entries.single()
return analyze(module) {
val scope = files.map { it as KtFile }.map { it.getFileSymbol().getFileScope() }.asCompositeScope()
KtObjCExportSession(KtObjCExportConfiguration()) {
scope.translateToObjCHeader().toString()
translateToObjCHeader(files.map { it as KtFile }).toString()
}
}
}
@@ -0,0 +1,31 @@
package org.jetbrains.kotlin.objcexport.tests
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.objcexport.analysisApiUtils.isTopLevel
import org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysis
import org.jetbrains.kotlin.objcexport.testUtils.getClassOrFail
import org.jetbrains.kotlin.objcexport.testUtils.getFunctionOrFail
import org.junit.jupiter.api.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class IsTopLevelTests(
private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis,
) {
@Test
fun `test - top level fun`() {
val ktFile = inlineSourceCodeAnalysis.createKtFile(
"""
fun topFun() {}
class TopClass {
fun classFun() {}
}
""".trimIndent()
)
analyze(ktFile) {
assertTrue(ktFile.getFunctionOrFail("topFun").isTopLevel)
assertFalse (ktFile.getClassOrFail("TopClass").getFunctionOrFail("classFun").isTopLevel)
}
}
}
@@ -18,7 +18,9 @@ interface ObjCExportClassOrProtocolName : ObjCExportName {
interface ObjCExportPropertyName : ObjCExportName
interface ObjCExportFunctionName: ObjCExportName
interface ObjCExportFunctionName : ObjCExportName
interface ObjCExportFileName : ObjCExportName
fun ObjCExportClassOrProtocolName(
swiftName: String,
@@ -34,7 +36,6 @@ private data class ObjCExportClassOrProtocolNameImpl(
override val swiftName: String,
override val objCName: String,
override val binaryName: String,
) : ObjCExportClassOrProtocolName
fun ObjCExportPropertyName(
@@ -53,6 +54,14 @@ fun ObjCExportFunctionName(
objCName = objCName
)
fun ObjCExportFileName(
swiftName: String,
objCName: String,
): ObjCExportFileName = ObjCExportFileNameImpl(
swiftName = swiftName,
objCName = objCName
)
private data class ObjCExportPropertyNameImpl(
override val swiftName: String,
override val objCName: String,
@@ -63,6 +72,11 @@ private data class ObjCExportFunctionNameImpl(
override val objCName: String,
) : ObjCExportFunctionName
private data class ObjCExportFileNameImpl(
override val swiftName: String,
override val objCName: String,
) : ObjCExportFileName
fun ObjCExportClassOrProtocolName.toNameAttributes(): List<String> = listOfNotNull(
binaryName.takeIf { it != objCName }?.let { objcRuntimeNameAttribute(it) },
@@ -77,7 +91,8 @@ fun objcRuntimeNameAttribute(name: String) = "objc_runtime_name(\"$name\")"
fun ObjCExportName.name(forSwift: Boolean) = swiftName.takeIf { forSwift } ?: objCName
private fun String.toIdentifier(): String = this.toValidObjCSwiftIdentifier()
@InternalKotlinNativeApi
fun String.toIdentifier(): String = this.toValidObjCSwiftIdentifier()
internal fun String.toValidObjCSwiftIdentifier(): String {
if (this.isEmpty()) return "__"