[ObjCExport] Add inner classes translation

KT-66339
This commit is contained in:
eugene.levenetc
2024-03-07 15:08:21 +01:00
committed by Space Team
parent 5fee662223
commit f7f779df53
20 changed files with 497 additions and 76 deletions
@@ -48,12 +48,15 @@ internal fun KtVariableLikeSymbol.getPropertyMethodBridge(): MethodBridge {
context(KtAnalysisSession) context(KtAnalysisSession)
private val KtCallableSymbol.receiverType: MethodBridgeReceiver private val KtCallableSymbol.receiverType: MethodBridgeReceiver
get() = if (isArrayConstructor) { get() {
MethodBridgeReceiver.Factory val isProperty = getContainingSymbol() as? KtPropertySymbol != null
} else if (!isConstructor && isTopLevel && !isExtension) { return if (isArrayConstructor) {
MethodBridgeReceiver.Static MethodBridgeReceiver.Factory
} else { } else if (!isConstructor && isTopLevel && !isExtension && !isProperty) {
MethodBridgeReceiver.Instance MethodBridgeReceiver.Static
} else {
MethodBridgeReceiver.Instance
}
} }
/** /**
@@ -148,12 +151,6 @@ private fun KtCallableSymbol.bridgeReturnType(): MethodBridge.ReturnValue {
return MethodBridge.ReturnValue.HashCode return MethodBridge.ReturnValue.HashCode
} }
//TODO: handle getter
// descriptor is PropertyGetterDescriptor -> {
// assert(!convertExceptionsToErrors)
// MethodBridge.ReturnValue.Mapped(bridgePropertyType(descriptor.correspondingProperty))
// }
if (returnType.isUnit || returnType.isNothing) { if (returnType.isUnit || returnType.isNothing) {
return if (hasThrowsAnnotation) { return if (hasThrowsAnnotation) {
MethodBridge.ReturnValue.WithError.Success MethodBridge.ReturnValue.WithError.Success
@@ -0,0 +1,18 @@
/*
* 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.analysisApiUtils
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.symbols.KtPropertyAccessorSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol
import org.jetbrains.kotlin.tooling.core.linearClosure
context(KtAnalysisSession)
internal fun KtPropertyAccessorSymbol.getPropertySymbol(): KtPropertySymbol {
return this.linearClosure<KtSymbol> { it.getContainingSymbol() }.filterIsInstance<KtPropertySymbol>().firstOrNull()
?: error("Missing '${KtPropertySymbol::class} on ${this.render()}")
}
@@ -3,6 +3,7 @@ package org.jetbrains.kotlin.objcexport
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.symbols.* import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportFunctionName import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportFunctionName
import org.jetbrains.kotlin.objcexport.analysisApiUtils.getPropertySymbol
context(KtAnalysisSession, KtObjCExportSession) context(KtAnalysisSession, KtObjCExportSession)
fun KtFunctionLikeSymbol.getObjCFunctionName(): ObjCExportFunctionName { fun KtFunctionLikeSymbol.getObjCFunctionName(): ObjCExportFunctionName {
@@ -13,20 +14,41 @@ fun KtFunctionLikeSymbol.getObjCFunctionName(): ObjCExportFunctionName {
) )
} }
context(KtAnalysisSession)
private fun KtFunctionLikeSymbol.getObjCName(resolvedNameAnnotation: KtResolvedObjCNameAnnotation?): String { private fun KtFunctionLikeSymbol.getObjCName(resolvedNameAnnotation: KtResolvedObjCNameAnnotation?): String {
return resolvedNameAnnotation?.objCName ?: translationName return resolvedNameAnnotation?.objCName ?: translationName
} }
context(KtAnalysisSession)
private fun KtFunctionLikeSymbol.getSwiftName(resolvedNameAnnotation: KtResolvedObjCNameAnnotation?): String { private fun KtFunctionLikeSymbol.getSwiftName(resolvedNameAnnotation: KtResolvedObjCNameAnnotation?): String {
return resolvedNameAnnotation?.swiftName ?: translationName return resolvedNameAnnotation?.swiftName ?: translationName
} }
context(KtAnalysisSession)
private val KtFunctionLikeSymbol.translationName: String private val KtFunctionLikeSymbol.translationName: String
get() = when (this) { get() {
is KtFunctionSymbol -> name.asString() return when (this) {
is KtConstructorSymbol -> "init" is KtFunctionSymbol -> name.asString()
is KtPropertyGetterSymbol -> "" is KtConstructorSymbol -> "init"
is KtAnonymousFunctionSymbol -> "" is KtPropertyAccessorSymbol -> this.objCPropertyName
is KtPropertySetterSymbol -> "" is KtAnonymousFunctionSymbol -> ""
is KtSamConstructorSymbol -> "" is KtSamConstructorSymbol -> ""
}
} }
context(KtAnalysisSession)
private val KtPropertyAccessorSymbol.objCPropertyName: String
get() {
return if (!this.getPropertySymbol().isObjCProperty) {
val containingSymbol = getContainingSymbol()
when (this) {
is KtPropertyGetterSymbol -> {
(containingSymbol as KtPropertySymbol).name.asString()
}
is KtPropertySetterSymbol -> {
"set" + ((containingSymbol as KtPropertySymbol).name.asString()).replaceFirstChar(kotlin.Char::uppercaseChar)
}
else -> ""
}
} else ""
}
@@ -0,0 +1,24 @@
package org.jetbrains.kotlin.objcexport
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.tooling.core.linearClosure
/**
* Property needs to be translated and support special naming in 2 cases:
* 1. It has no receiver type and no extension
* 2. It is a property of an inner class
*
* See K1 [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportMapperKt.isObjCProperty]
*/
context(KtAnalysisSession)
internal val KtPropertySymbol.isObjCProperty: Boolean
get() {
val hasReceiver = receiverParameter != null && !isExtension
return !hasReceiver && !isPropertyInInnerClass
}
context(KtAnalysisSession)
private val KtPropertySymbol.isPropertyInInnerClass: Boolean
get() = linearClosure<KtSymbol> { symbol -> symbol.getContainingSymbol() }
.any { it is KtNamedClassOrObjectSymbol && it.isInner }
@@ -36,7 +36,7 @@ fun KtClassOrObjectSymbol.translateToObjCClass(): ObjCClass? {
this += getCallableSymbolsForObjCMemberTranslation() this += getCallableSymbolsForObjCMemberTranslation()
.sortedWith(StableCallableOrder) .sortedWith(StableCallableOrder)
.mapNotNull { it.translateToObjCExportStub() } .flatMap { it.translateToObjCExportStub() }
if (classKind == KtClassKind.ENUM_CLASS) { if (classKind == KtClassKind.ENUM_CLASS) {
this += translateEnumMembers() this += translateEnumMembers()
@@ -4,14 +4,24 @@ import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.symbols.* import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCClass import org.jetbrains.kotlin.backend.konan.objcexport.ObjCClass
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportStub import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportStub
import org.jetbrains.kotlin.utils.addIfNotNull
context(KtAnalysisSession, KtObjCExportSession) context(KtAnalysisSession, KtObjCExportSession)
internal fun KtCallableSymbol.translateToObjCExportStub(): ObjCExportStub? { internal fun KtCallableSymbol.translateToObjCExportStub(): List<ObjCExportStub> {
return when (this) { val result = mutableListOf<ObjCExportStub>()
is KtPropertySymbol -> translateToObjCProperty() when (this) {
is KtFunctionSymbol -> translateToObjCMethod() is KtPropertySymbol -> {
else -> null if (isObjCProperty) {
result.addIfNotNull(translateToObjCProperty())
} else {
result.addIfNotNull(this.getter?.translateToObjCMethod())
result.addIfNotNull(this.setter?.translateToObjCMethod())
}
}
is KtFunctionSymbol -> result.addIfNotNull(translateToObjCMethod())
else -> Unit
} }
return result
} }
context(KtAnalysisSession, KtObjCExportSession) context(KtAnalysisSession, KtObjCExportSession)
@@ -64,7 +64,7 @@ fun KtResolvedObjCExportFile.translateToObjCExtensionFacades(): List<ObjCInterfa
origin = null, origin = null,
attributes = emptyList(), attributes = emptyList(),
superProtocols = emptyList(), superProtocols = emptyList(),
members = extensionSymbols.mapNotNull { ext -> ext.translateToObjCExportStub() }, members = extensionSymbols.flatMap { ext -> ext.translateToObjCExportStub() },
categoryName = extensionsCategoryName, categoryName = extensionsCategoryName,
generics = emptyList(), generics = emptyList(),
superClass = null, superClass = null,
@@ -6,7 +6,6 @@ import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.annotations.annotationInfos import org.jetbrains.kotlin.analysis.api.annotations.annotationInfos
import org.jetbrains.kotlin.analysis.api.symbols.* import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtNamedSymbol import org.jetbrains.kotlin.analysis.api.symbols.markers.KtNamedSymbol
import org.jetbrains.kotlin.backend.konan.InternalKotlinNativeApi
import org.jetbrains.kotlin.backend.konan.KonanFqNames import org.jetbrains.kotlin.backend.konan.KonanFqNames
import org.jetbrains.kotlin.backend.konan.objcexport.* import org.jetbrains.kotlin.backend.konan.objcexport.*
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
@@ -20,10 +19,10 @@ internal val KtCallableSymbol.isConstructor: Boolean
get() = this is KtConstructorSymbol get() = this is KtConstructorSymbol
context(KtAnalysisSession, KtObjCExportSession) context(KtAnalysisSession, KtObjCExportSession)
fun KtFunctionSymbol.translateToObjCMethod(): ObjCMethod? { fun KtFunctionLikeSymbol.translateToObjCMethod(): ObjCMethod? {
if (!isVisibleInObjC()) return null if (!isVisibleInObjC()) return null
if (isFakeOverride) return null if (isFakeOverride) return null
if (isClone) return null if (this is KtFunctionSymbol && isClone) return null
return buildObjCMethod() return buildObjCMethod()
} }
@@ -48,7 +47,7 @@ internal fun KtFunctionLikeSymbol.buildObjCMethod(
val returnType: ObjCType = mapReturnType(bridge.returnBridge) val returnType: ObjCType = mapReturnType(bridge.returnBridge)
val parameters = translateToObjCParameters(bridge) val parameters = translateToObjCParameters(bridge)
val selector = getSelector(bridge) val selector = getSelector(bridge)
val selectors: List<String> = splitSelector(selector) val selectors = splitSelector(selector)
val swiftName = getSwiftName(bridge) val swiftName = getSwiftName(bridge)
val attributes = mutableListOf<String>() val attributes = mutableListOf<String>()
val returnBridge = bridge.returnBridge val returnBridge = bridge.returnBridge
@@ -127,15 +126,18 @@ internal fun KtFunctionLikeSymbol.getSwiftName(methodBridge: MethodBridge): Stri
append(getMangledName(forSwift = true)) append(getMangledName(forSwift = true))
append("(") append("(")
parameters@ for ((bridge, symbol) in parameters) { parameters@ for ((bridge, parameter: KtObjCParameterData?) in parameters) {
val label = when (bridge) { val label = when (bridge) {
is MethodBridgeValueParameter.Mapped -> when { is MethodBridgeValueParameter.Mapped -> when {
//it is ReceiverParameterDescriptor -> it.getObjCName().asIdentifier(true) { "_" } parameter?.isReceiver == true -> "_"
method is KtPropertySetterSymbol -> when (parameters.size) { method is KtPropertySetterSymbol -> when (parameters.size) {
1 -> "_" 1 -> "_"
else -> "value" else -> "value"
} }
else -> symbol!!.name else -> {
if (parameter == null) continue@parameters
else if (parameter.isReceiver) "_" else parameter.name
}
} }
MethodBridgeValueParameter.ErrorOutParameter -> continue@parameters MethodBridgeValueParameter.ErrorOutParameter -> continue@parameters
is MethodBridgeValueParameter.SuspendCompletion -> "completionHandler" is MethodBridgeValueParameter.SuspendCompletion -> "completionHandler"
@@ -149,7 +151,6 @@ internal fun KtFunctionLikeSymbol.getSwiftName(methodBridge: MethodBridge): Stri
} }
return sb.toString() return sb.toString()
} }
@@ -190,23 +191,23 @@ fun KtFunctionLikeSymbol.getSelector(methodBridge: MethodBridge): String {
} }
val parameters = methodBridge.valueParametersAssociated(this) val parameters = methodBridge.valueParametersAssociated(this)
val method = this val method = this
val sb = StringBuilder() val sb = StringBuilder()
sb.append(method.getMangledName(forSwift = false)) sb.append(method.getMangledName(forSwift = false))
parameters.forEachIndexed { index, (bridge, typeParameterSymbol) -> parameters.forEachIndexed { index, (bridge, parameter) ->
val name = when (bridge) { val name = when (bridge) {
is MethodBridgeValueParameter.Mapped -> when { is MethodBridgeValueParameter.Mapped -> when {
parameter?.isReceiver == true -> ""
method is KtPropertySetterSymbol -> when (parameters.size) { method is KtPropertySetterSymbol -> when (parameters.size) {
1 -> "" 1 -> ""
else -> "value" else -> "value"
} }
else -> { else -> {
typeParameterSymbol!!.name.toString() if (parameter == null) return@forEachIndexed
else if (parameter.isReceiver) "" else parameter.name.toString()
} }
} }
MethodBridgeValueParameter.ErrorOutParameter -> "error" MethodBridgeValueParameter.ErrorOutParameter -> "error"
@@ -255,26 +256,6 @@ private fun String.handleSpecialNames(prefix: String): String {
return this return this
} }
/**
* [org.jetbrains.kotlin.backend.konan.objcexport.MethodBrideExtensionsKt.valueParametersAssociated]
*/
@InternalKotlinNativeApi
fun MethodBridge.valueParametersAssociated(
function: KtFunctionLikeSymbol,
): List<Pair<MethodBridgeValueParameter, KtValueParameterSymbol?>> {
val allParameters = function.valueParameters
return this.valueParameters.mapIndexed { index, valueParameterBridge ->
when (valueParameterBridge) {
is MethodBridgeValueParameter.Mapped -> valueParameterBridge to allParameters[index]
is MethodBridgeValueParameter.SuspendCompletion,
is MethodBridgeValueParameter.ErrorOutParameter,
-> valueParameterBridge to null
}
}
}
private fun String.startsWithWords(words: String) = this.startsWith(words) && 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())
@@ -33,7 +33,7 @@ fun KtClassOrObjectSymbol.translateToObjCObject(): ObjCClass? {
objectMembers += getDefaultMembers() objectMembers += getDefaultMembers()
objectMembers += getDeclaredMemberScope().getCallableSymbols() objectMembers += getDeclaredMemberScope().getCallableSymbols()
.sortedWith(StableCallableOrder) .sortedWith(StableCallableOrder)
.mapNotNull { it.translateToObjCExportStub() } .flatMap { it.translateToObjCExportStub() }
return ObjCInterfaceImpl( return ObjCInterfaceImpl(
name = name.objCName, name = name.objCName,
@@ -5,11 +5,9 @@ import org.jetbrains.kotlin.analysis.api.components.buildClassType
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionLikeSymbol import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionLikeSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySetterSymbol import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySetterSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtTypeParameterSymbol import org.jetbrains.kotlin.analysis.api.symbols.KtTypeParameterSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtValueParameterSymbol
import org.jetbrains.kotlin.backend.konan.cKeywords import org.jetbrains.kotlin.backend.konan.cKeywords
import org.jetbrains.kotlin.backend.konan.objcexport.* import org.jetbrains.kotlin.backend.konan.objcexport.*
import org.jetbrains.kotlin.name.StandardClassIds import org.jetbrains.kotlin.name.StandardClassIds
import org.jetbrains.kotlin.psi.KtParameter
context(KtAnalysisSession, KtObjCExportSession) context(KtAnalysisSession, KtObjCExportSession)
internal fun KtFunctionLikeSymbol.translateToObjCParameters(baseMethodBridge: MethodBridge): List<ObjCParameter> { internal fun KtFunctionLikeSymbol.translateToObjCParameters(baseMethodBridge: MethodBridge): List<ObjCParameter> {
@@ -27,13 +25,19 @@ internal fun KtFunctionLikeSymbol.translateToObjCParameters(baseMethodBridge: Me
val usedNames = mutableSetOf<String>() val usedNames = mutableSetOf<String>()
valueParametersAssociated.forEach { (bridge: MethodBridgeValueParameter, parameter: KtValueParameterSymbol?) -> valueParametersAssociated.forEach { (bridge: MethodBridgeValueParameter, parameter: KtObjCParameterData?) ->
val candidateName: String = when (bridge) { val candidateName = when (bridge) {
is MethodBridgeValueParameter.Mapped -> { is MethodBridgeValueParameter.Mapped -> {
if (parameter == null) throw IllegalStateException("Parameter shouldn't be null") if (parameter == null) return@forEach
when { when {
this is KtPropertySetterSymbol -> "value" this is KtPropertySetterSymbol -> {
else -> parameter.name.toString() if (parameter.isReceiver) "receiver"
else "value"
}
else -> {
if (parameter.isReceiver) "receiver"
else parameter.name.toString()
}
} }
} }
MethodBridgeValueParameter.ErrorOutParameter -> "error" MethodBridgeValueParameter.ErrorOutParameter -> "error"
@@ -45,10 +49,10 @@ internal fun KtFunctionLikeSymbol.translateToObjCParameters(baseMethodBridge: Me
val type = when (bridge) { val type = when (bridge) {
is MethodBridgeValueParameter.Mapped -> { is MethodBridgeValueParameter.Mapped -> {
val returnType = parameter!!.returnType val returnType = parameter!!.type
if (parameter.isVararg) { if (parameter.isVararg) {
//vararg is a special case, [parameter.returnType] is T, we need Array<T> //vararg is a special case, [parameter.returnType] is T, we need Array<T>
buildClassType(StandardClassIds.Array) { argument(parameter.returnType) }.translateToObjCType(bridge.bridge) buildClassType(StandardClassIds.Array) { argument(parameter.type) }.translateToObjCType(bridge.bridge)
} else { } else {
returnType.translateToObjCType(bridge.bridge) returnType.translateToObjCType(bridge.bridge)
} }
@@ -28,7 +28,7 @@ fun KtClassOrObjectSymbol.translateToObjCProtocol(): ObjCProtocol? {
val members = getCallableSymbolsForObjCMemberTranslation() val members = getCallableSymbolsForObjCMemberTranslation()
.filter { it.isObjCBaseCallable() } .filter { it.isObjCBaseCallable() }
.sortedWith(StableCallableOrder) .sortedWith(StableCallableOrder)
.mapNotNull { it.translateToObjCExportStub() } .flatMap { it.translateToObjCExportStub() }
val comment: ObjCComment? = annotationsList.translateToObjCComment() val comment: ObjCComment? = annotationsList.translateToObjCComment()
@@ -60,7 +60,7 @@ fun KtResolvedObjCExportFile.translateToObjCTopLevelFacade(): ObjCInterface? {
origin = null, origin = null,
attributes = listOf(OBJC_SUBCLASSING_RESTRICTED) + fileName.toNameAttributes(), attributes = listOf(OBJC_SUBCLASSING_RESTRICTED) + fileName.toNameAttributes(),
superProtocols = emptyList(), superProtocols = emptyList(),
members = extensions.mapNotNull { it.translateToObjCExportStub() }, members = extensions.flatMap { it.translateToObjCExportStub() },
categoryName = null, categoryName = null,
generics = emptyList(), generics = emptyList(),
superClass = getDefaultSuperClassOrProtocolName().objCName, superClass = getDefaultSuperClassOrProtocolName().objCName,
@@ -6,13 +6,12 @@
package org.jetbrains.kotlin.objcexport package org.jetbrains.kotlin.objcexport
import org.jetbrains.kotlin.analysis.api.symbols.* import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtNamedSymbol
internal val StableFileOrder: Comparator<KtObjCExportFile> internal val StableFileOrder: Comparator<KtObjCExportFile>
get() = compareBy<KtObjCExportFile> { file -> file.packageFqName.asString() } get() = compareBy<KtObjCExportFile> { file -> file.packageFqName.asString() }
.thenComparing { file -> file.fileName } .thenComparing { file -> file.fileName }
internal val StablePropertyOrder: Comparator<KtPropertySymbol> = compareBy { it.name }
internal val StableFunctionOrder: Comparator<KtFunctionSymbol> internal val StableFunctionOrder: Comparator<KtFunctionSymbol>
get() = compareBy( get() = compareBy(
{ it.isConstructor }, { it.isConstructor },
@@ -42,6 +41,20 @@ internal val StableClassifierOrder: Comparator<KtClassifierSymbol> =
else "" else ""
} }
//Figure out when name is special and enable logic. See KT-66510
// when (it) {
// is KtPropertySymbol -> {
// /**
// * K1 property names are special: [org.jetbrains.kotlin.name.Name.special] == true
// * So we need to wrap setter/getter
// */
// if (it.setter == null) "<get-${it.name}>"
// else "<set-${it.name}>"
// }
// else -> it.name.toString()
// }
internal val StableNamedOrder: Comparator<KtNamedSymbol> = compareBy { it.name.toString() }
internal val StableCallableOrder: Comparator<KtCallableSymbol> = compareBy<KtCallableSymbol> { internal val StableCallableOrder: Comparator<KtCallableSymbol> = compareBy<KtCallableSymbol> {
when (it) { when (it) {
is KtConstructorSymbol -> 0 is KtConstructorSymbol -> 0
@@ -49,12 +62,11 @@ internal val StableCallableOrder: Comparator<KtCallableSymbol> = compareBy<KtCal
is KtPropertySymbol -> 2 is KtPropertySymbol -> 2
else -> 3 else -> 3
} }
} }.thenComparing(StableConstructorOrder)
.thenComparing(StableConstructorOrder)
.thenComparing(StablePropertyOrder)
.thenComparing(StableFunctionOrder) .thenComparing(StableFunctionOrder)
.thenComparing(StableNamedOrder)
private inline fun <T, reified R> Comparator<T>.thenComparing(comparator: Comparator<R>): Comparator<T> where R : T { private inline fun <T, reified R> Comparator<T>.thenComparing(comparator: Comparator<R>): Comparator<T> {
return thenComparing { a, b -> return thenComparing { a, b ->
if (a is R && b is R) comparator.compare(a, b) else 0 if (a is R && b is R) comparator.compare(a, b) else 0
} }
@@ -0,0 +1,95 @@
package org.jetbrains.kotlin.objcexport
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.analysis.api.types.KtType
import org.jetbrains.kotlin.backend.konan.objcexport.MethodBridge
import org.jetbrains.kotlin.backend.konan.objcexport.MethodBridgeValueParameter
import org.jetbrains.kotlin.backend.konan.objcexport.ReferenceBridge
import org.jetbrains.kotlin.name.Name
/**
* See K1 implementation [org.jetbrains.kotlin.backend.konan.objcexport.MethodBrideExtensionsKt.valueParametersAssociated]
*/
context(KtAnalysisSession)
internal fun MethodBridge.valueParametersAssociated(
function: KtFunctionLikeSymbol,
): List<Pair<MethodBridgeValueParameter, KtObjCParameterData?>> {
val result = mutableListOf<Pair<MethodBridgeValueParameter, KtObjCParameterData?>>()
val valueParameters = function.valueParameters
val receiverType = function.objCReceiverType
val receiverTypeName = receiverType?.expandedClassSymbol?.name
if (receiverType != null && receiverTypeName != null) {
result.add(
MethodBridgeValueParameter.Mapped(ReferenceBridge) to KtObjCParameterData(
name = receiverTypeName,
isVararg = false,
type = receiverType,
isReceiver = true
)
)
}
this.valueParameters.forEachIndexed { index, valueParameterBridge ->
if (valueParameterBridge is MethodBridgeValueParameter.Mapped) {
result.add(
valueParameterBridge to KtObjCParameterData(
name = valueParameters[index].name,
isVararg = valueParameters[index].isVararg,
type = valueParameters[index].returnType,
isReceiver = false
)
)
} else {
result.add(valueParameterBridge to null)
}
}
return result
}
internal data class KtObjCParameterData(
val name: Name,
val isVararg: Boolean,
val type: KtType,
val isReceiver: Boolean,
)
/**
* Not null in 3 cases:
* 1. constructor of inner class
* 2. function extension of inner class
* 3. property extension of inner class
*
* Members with non null [objCReceiverType] will have name `receiver`:
* ```objective-c
* - (int32_t)foo:(NSString *)receiver __attribute__((swift_name("foo(_:)")));
* ```
*
* Also see [isObjCProperty]
*/
context(KtAnalysisSession)
internal val KtFunctionLikeSymbol.objCReceiverType: KtType?
get() {
return if (isConstructor) {
/**
* Edge case for supporting inner classes parameter.
* See details at KT-66339
*/
@Suppress("DEPRECATION")
getDispatchReceiverType()
} else if (isExtension) {
if ((getContainingSymbol() as? KtNamedClassOrObjectSymbol)?.isInner == true) receiverParameter?.type
else null
} else if (this is KtPropertyGetterSymbol || this is KtPropertySetterSymbol) {
val property = this.getContainingSymbol() as KtPropertySymbol
val isExtension = property.isExtension
val isInner = (property.getContainingSymbol() as? KtNamedClassOrObjectSymbol)?.isInner == true
if (isExtension && isInner) property.receiverType else null
} else {
null
}
}
@@ -0,0 +1,152 @@
/*
* 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.tests
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.objcexport.objCReceiverType
import org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysis
import org.jetbrains.kotlin.objcexport.testUtils.getClassOrFail
import org.jetbrains.kotlin.objcexport.testUtils.getFunctionOrFail
import org.jetbrains.kotlin.objcexport.testUtils.getPropertyOrFail
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
class ObjCReceiverTypeTest(
private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis,
) {
@Test
fun `test - inner constructor`() {
val file = inlineSourceCodeAnalysis.createKtFile(
"""
class Outer {
inner class Inner
}
""".trimIndent()
)
analyze(file) {
val outerClass = file.getClassOrFail("Outer")
val innerClass = outerClass.getMemberScope().getClassOrFail("Inner")
val innerClassConstructor = innerClass.getMemberScope().getConstructors().first()
assertEquals(
innerClassConstructor.objCReceiverType?.expandedClassSymbol?.classIdIfNonLocal,
outerClass.classIdIfNonLocal
)
}
}
@Test
fun `test - inner function`() {
val file = inlineSourceCodeAnalysis.createKtFile(
"""
class Outer {
inner class Inner {
fun String.foo() = Unit
}
}
""".trimIndent()
)
analyze(file) {
val outerClass = file.getClassOrFail("Outer")
val innerClass = outerClass.getMemberScope().getClassOrFail("Inner")
val foo = innerClass.getMemberScope().getFunctionOrFail("foo")
assertEquals(
ClassId.topLevel(StandardNames.FqNames.string.toSafe()),
foo.objCReceiverType?.expandedClassSymbol?.classIdIfNonLocal
)
}
}
@Test
fun `test - inner getter`() {
val file = inlineSourceCodeAnalysis.createKtFile(
"""
class Outer {
inner class Inner {
val Boolean.prop: Boolean
get() = false
}
}
""".trimIndent()
)
analyze(file) {
val outerClass = file.getClassOrFail("Outer")
val innerClass = outerClass.getMemberScope().getClassOrFail("Inner")
val getter = innerClass.getMemberScope().getPropertyOrFail("prop").getter
assertEquals(
ClassId.topLevel(StandardNames.FqNames._boolean.toSafe()),
getter?.objCReceiverType?.expandedClassSymbol?.classIdIfNonLocal
)
}
}
@Test
fun `test - inner setter`() {
val file = inlineSourceCodeAnalysis.createKtFile(
"""
class Outer {
inner class Inner {
var String.prop: Int
get() = 42
set(value) {}
}
}
""".trimIndent()
)
analyze(file) {
val outerClass = file.getClassOrFail("Outer")
val innerClass = outerClass.getMemberScope().getClassOrFail("Inner")
val setter = innerClass.getMemberScope().getPropertyOrFail("prop").setter
assertEquals(
ClassId.topLevel(StandardNames.FqNames.string.toSafe()),
setter?.objCReceiverType?.expandedClassSymbol?.classIdIfNonLocal
)
}
}
@Test
fun `test - regular extensions`() {
val file = inlineSourceCodeAnalysis.createKtFile(
"""
class Foo {
fun Boolean.foo() = Unit
var String.prop: Int
get() = 42
set(value) {}
}
""".trimIndent()
)
analyze(file) {
val fooClass = file.getClassOrFail("Foo")
val foo = fooClass.getMemberScope().getFunctionOrFail("foo")
val setter = fooClass.getMemberScope().getPropertyOrFail("prop").setter
val getter = fooClass.getMemberScope().getPropertyOrFail("prop").getter
assertNull(foo.objCReceiverType)
assertNull(setter?.objCReceiverType)
assertNull(getter?.objCReceiverType)
}
}
}
@@ -356,6 +356,21 @@ class ObjCExportHeaderGeneratorTest(private val generator: HeaderGenerator) {
doTest(headersTestDataDir.resolve("suspendFunction")) doTest(headersTestDataDir.resolve("suspendFunction"))
} }
@Test
fun `test - innerClass`() {
doTest(headersTestDataDir.resolve("innerClass"))
}
/**
* Works except properties sorting with special name [org.jetbrains.kotlin.name.Name.special]
* See KT-66510
*/
@Test
@TodoAnalysisApi
fun `test - innerClassWithExtensionFunction`() {
doTest(headersTestDataDir.resolve("innerClassWithExtensionFunction"))
}
private fun doTest(root: File, configuration: Configuration = Configuration()) { private fun doTest(root: File, configuration: Configuration = Configuration()) {
if (!root.isDirectory) fail("Expected ${root.absolutePath} to be directory") if (!root.isDirectory) fail("Expected ${root.absolutePath} to be directory")
val generatedHeaders = generator.generateHeaders(root, configuration).toString() val generatedHeaders = generator.generateHeaders(root, configuration).toString()
@@ -0,0 +1,37 @@
#import <Foundation/NSArray.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSError.h>
#import <Foundation/NSObject.h>
#import <Foundation/NSSet.h>
#import <Foundation/NSString.h>
#import <Foundation/NSValue.h>
@class Foo;
NS_ASSUME_NONNULL_BEGIN
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-warning-option"
#pragma clang diagnostic ignored "-Wincompatible-property-type"
#pragma clang diagnostic ignored "-Wnullability"
#pragma push_macro("_Nullable_result")
#if !__has_feature(nullability_nullable_result)
#undef _Nullable_result
#define _Nullable_result _Nullable
#endif
__attribute__((objc_subclassing_restricted))
@interface Foo : Base
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
@end
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("Foo.Inner")))
@interface FooInner : Base
- (instancetype)initWith:(Foo *)receiver __attribute__((swift_name("init(_:)"))) __attribute__((objc_designated_initializer));
@end
#pragma pop_macro("_Nullable_result")
#pragma clang diagnostic pop
NS_ASSUME_NONNULL_END
@@ -0,0 +1,3 @@
class Foo {
inner class Inner
}
@@ -0,0 +1,41 @@
#import <Foundation/NSArray.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSError.h>
#import <Foundation/NSObject.h>
#import <Foundation/NSSet.h>
#import <Foundation/NSString.h>
#import <Foundation/NSValue.h>
@class Foo;
NS_ASSUME_NONNULL_BEGIN
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-warning-option"
#pragma clang diagnostic ignored "-Wincompatible-property-type"
#pragma clang diagnostic ignored "-Wnullability"
#pragma push_macro("_Nullable_result")
#if !__has_feature(nullability_nullable_result)
#undef _Nullable_result
#define _Nullable_result _Nullable
#endif
__attribute__((objc_subclassing_restricted))
@interface Foo : Base
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
@end
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("Foo.Inner")))
@interface FooInner : Base
- (instancetype)initWith:(Foo *)receiver __attribute__((swift_name("init(_:)"))) __attribute__((objc_designated_initializer));
- (int32_t)propSet:(Foo *)receiver __attribute__((swift_name("propSet(_:)")));
- (BOOL)propVal:(Foo *)receiver __attribute__((swift_name("propVal(_:)")));
- (void)setPropSet:(Foo *)receiver value:(int32_t)value __attribute__((swift_name("setPropSet(_:value:)")));
- (int32_t)foo:(NSString *)receiver __attribute__((swift_name("foo(_:)")));
@end
#pragma pop_macro("_Nullable_result")
#pragma clang diagnostic pop
NS_ASSUME_NONNULL_END
@@ -0,0 +1,10 @@
class Foo {
inner class Inner {
val Foo.propVal: Boolean
get() = false
fun String.foo() = 42
var Foo.propSet: Int
get() = 42
set(value) {}
}
}