diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/KtObjCExportConfiguration.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/KtObjCExportConfiguration.kt index debeaf94f53..8c232174c94 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/KtObjCExportConfiguration.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/KtObjCExportConfiguration.kt @@ -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 ) diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/members.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/members.kt new file mode 100644 index 00000000000..a7019747fe3 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/members.kt @@ -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 { + return getMemberScope() + .getAllSymbols() + .sortedBy { sortMembers(it) } + .filterIsInstance() + .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 +} diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getObjCFunctionName.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getObjCFunctionName.kt new file mode 100644 index 00000000000..e9b3e0377fb --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getObjCFunctionName.kt @@ -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 + ) +} \ No newline at end of file diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getObjCKotlinStdlibClassOrProtocolName.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getObjCKotlinStdlibClassOrProtocolName.kt new file mode 100644 index 00000000000..d8a5e64d3fa --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getObjCKotlinStdlibClassOrProtocolName.kt @@ -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" +) diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCClass.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCClass.kt new file mode 100644 index 00000000000..fdee01cad3e --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCClass.kt @@ -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 = superProtocols() + val members: List = members().flatMap { it.translateToObjCExportStubs() } + val categoryName: String? = null + val generics: List = emptyList() + val superClassGenerics: List = 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 +) \ No newline at end of file diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCConstructor.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCConstructor.kt new file mode 100644 index 00000000000..16611dbf4e6 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCConstructor.kt @@ -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 { + if (!isVisibleInObjC()) return emptyList() + val result = mutableListOf() + + result.add(buildObjCMethod()) + + result.add( + ObjCMethod( + null, + null, + false, + ObjCInstanceType, + listOf("new"), + emptyList(), + listOf( + "availability(swift, unavailable, message=\"use object initializers instead\")" + ) + ) + ) + + return result +} \ No newline at end of file diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCFunctionType.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCFunctionType.kt new file mode 100644 index 00000000000..5d9a88685cb --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCFunctionType.kt @@ -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() +} diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCHeader.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCHeader.kt index 794ddb6cd00..b3ba6caff01 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCHeader.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCHeader.kt @@ -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 { 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() } -} +} \ No newline at end of file diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCMethod.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCMethod.kt new file mode 100644 index 00000000000..1ca7f7e9419 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCMethod.kt @@ -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 = splitSelector(selector) + val swiftName = getSwiftName() + val attributes = mutableListOf() + 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 { + fun unifyName(initialName: String, usedNames: Set): String { + var unique = initialName.toValidObjCSwiftIdentifier() + while (unique in usedNames || unique in cKeywords) { + unique += "_" + } + return unique + } + + val valueParametersAssociated = baseMethodBridge.valueParametersAssociated(method) + + val parameters = mutableListOf() + + val usedNames = mutableSetOf() + + 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): 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 { + + 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): List { + return emptyList() +} + +private fun mustBeDocumentedAttributeList(annotations: List): List { + 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 { + 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 { + 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 { + return if (!selector.endsWith(":")) { + listOf(selector) + } else { + selector.trimEnd(':').split(':').map { "$it:" } + } +} + +/** + * Not implemented [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamerImpl.getPredefined] + */ +private fun getPredefined(method: KtFunctionLikeSymbol, predefinedForAny: Map): 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> { + + 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() + + 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 + } +} \ No newline at end of file diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCProperty.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCProperty.kt index 62b9434c68f..306ef87944b 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCProperty.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCProperty.kt @@ -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() + 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 + ) } \ No newline at end of file diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCProtocol.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCProtocol.kt index 9718e5a10df..1d43d9ed8f6 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCProtocol.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCProtocol.kt @@ -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() - .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 { + 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() +} \ No newline at end of file diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCReferenceType.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCReferenceType.kt new file mode 100644 index 00000000000..1c52f4baae9 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCReferenceType.kt @@ -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().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 + } +} diff --git a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportName.kt b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportName.kt index ac690c62760..7481508742d 100644 --- a/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportName.kt +++ b/native/objcexport-header-generator/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportName.kt @@ -5,6 +5,8 @@ package org.jetbrains.kotlin.backend.konan.objcexport +import org.jetbrains.kotlin.backend.konan.InternalKotlinNativeApi + sealed interface ObjCExportName { val swiftName: String val objCName: String @@ -16,6 +18,8 @@ interface ObjCExportClassOrProtocolName : ObjCExportName { interface ObjCExportPropertyName : ObjCExportName +interface ObjCExportFunctionName: ObjCExportName + fun ObjCExportClassOrProtocolName( swiftName: String, objCName: String, @@ -30,6 +34,7 @@ private data class ObjCExportClassOrProtocolNameImpl( override val swiftName: String, override val objCName: String, override val binaryName: String, + ) : ObjCExportClassOrProtocolName fun ObjCExportPropertyName( @@ -40,16 +45,44 @@ fun ObjCExportPropertyName( objCName = objCName ) +fun ObjCExportFunctionName( + swiftName: String, + objCName: String, +): ObjCExportFunctionName = ObjCExportFunctionNameImpl( + swiftName = swiftName, + objCName = objCName +) + private data class ObjCExportPropertyNameImpl( override val swiftName: String, override val objCName: String, ) : ObjCExportPropertyName +private data class ObjCExportFunctionNameImpl( + override val swiftName: String, + override val objCName: String, +) : ObjCExportFunctionName + fun ObjCExportClassOrProtocolName.toNameAttributes(): List = listOfNotNull( binaryName.takeIf { it != objCName }?.let { objcRuntimeNameAttribute(it) }, swiftName.takeIf { it != objCName }?.let { swiftNameAttribute(it) } ) -private fun swiftNameAttribute(swiftName: String) = "swift_name(\"$swiftName\")" -private fun objcRuntimeNameAttribute(name: String) = "objc_runtime_name(\"$name\")" \ No newline at end of file +@InternalKotlinNativeApi +fun swiftNameAttribute(swiftName: String) = "swift_name(\"$swiftName\")" + +@InternalKotlinNativeApi +fun objcRuntimeNameAttribute(name: String) = "objc_runtime_name(\"$name\")" + +fun ObjCExportName.name(forSwift: Boolean) = swiftName.takeIf { forSwift } ?: objCName + +private fun String.toIdentifier(): String = this.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 } +} \ No newline at end of file