diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getMethodBridge.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getMethodBridge.kt index 0f0446e2827..4475bc55df1 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getMethodBridge.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getMethodBridge.kt @@ -48,12 +48,15 @@ internal fun KtVariableLikeSymbol.getPropertyMethodBridge(): MethodBridge { context(KtAnalysisSession) private val KtCallableSymbol.receiverType: MethodBridgeReceiver - get() = if (isArrayConstructor) { - MethodBridgeReceiver.Factory - } else if (!isConstructor && isTopLevel && !isExtension) { - MethodBridgeReceiver.Static - } else { - MethodBridgeReceiver.Instance + get() { + val isProperty = getContainingSymbol() as? KtPropertySymbol != null + return if (isArrayConstructor) { + MethodBridgeReceiver.Factory + } else if (!isConstructor && isTopLevel && !isExtension && !isProperty) { + MethodBridgeReceiver.Static + } else { + MethodBridgeReceiver.Instance + } } /** @@ -148,12 +151,6 @@ private fun KtCallableSymbol.bridgeReturnType(): MethodBridge.ReturnValue { return MethodBridge.ReturnValue.HashCode } - //TODO: handle getter -// descriptor is PropertyGetterDescriptor -> { -// assert(!convertExceptionsToErrors) -// MethodBridge.ReturnValue.Mapped(bridgePropertyType(descriptor.correspondingProperty)) -// } - if (returnType.isUnit || returnType.isNothing) { return if (hasThrowsAnnotation) { MethodBridge.ReturnValue.WithError.Success diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getPropertySymbol.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getPropertySymbol.kt new file mode 100644 index 00000000000..d78309f1780 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getPropertySymbol.kt @@ -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 { it.getContainingSymbol() }.filterIsInstance().firstOrNull() + ?: error("Missing '${KtPropertySymbol::class} on ${this.render()}") +} 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 index 7afe1f3ef2d..4b15d3823eb 100644 --- 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 @@ -3,6 +3,7 @@ package org.jetbrains.kotlin.objcexport import org.jetbrains.kotlin.analysis.api.KtAnalysisSession import org.jetbrains.kotlin.analysis.api.symbols.* import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportFunctionName +import org.jetbrains.kotlin.objcexport.analysisApiUtils.getPropertySymbol context(KtAnalysisSession, KtObjCExportSession) fun KtFunctionLikeSymbol.getObjCFunctionName(): ObjCExportFunctionName { @@ -13,20 +14,41 @@ fun KtFunctionLikeSymbol.getObjCFunctionName(): ObjCExportFunctionName { ) } +context(KtAnalysisSession) private fun KtFunctionLikeSymbol.getObjCName(resolvedNameAnnotation: KtResolvedObjCNameAnnotation?): String { return resolvedNameAnnotation?.objCName ?: translationName } +context(KtAnalysisSession) private fun KtFunctionLikeSymbol.getSwiftName(resolvedNameAnnotation: KtResolvedObjCNameAnnotation?): String { return resolvedNameAnnotation?.swiftName ?: translationName } +context(KtAnalysisSession) private val KtFunctionLikeSymbol.translationName: String - get() = when (this) { - is KtFunctionSymbol -> name.asString() - is KtConstructorSymbol -> "init" - is KtPropertyGetterSymbol -> "" - is KtAnonymousFunctionSymbol -> "" - is KtPropertySetterSymbol -> "" - is KtSamConstructorSymbol -> "" + get() { + return when (this) { + is KtFunctionSymbol -> name.asString() + is KtConstructorSymbol -> "init" + is KtPropertyAccessorSymbol -> this.objCPropertyName + is KtAnonymousFunctionSymbol -> "" + 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 "" + } \ No newline at end of file diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/isObjCProperty.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/isObjCProperty.kt new file mode 100644 index 00000000000..e463c3cadb7 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/isObjCProperty.kt @@ -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 { symbol -> symbol.getContainingSymbol() } + .any { it is KtNamedClassOrObjectSymbol && it.isInner } 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 index 6b70a308d11..72703897619 100644 --- 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 @@ -36,7 +36,7 @@ fun KtClassOrObjectSymbol.translateToObjCClass(): ObjCClass? { this += getCallableSymbolsForObjCMemberTranslation() .sortedWith(StableCallableOrder) - .mapNotNull { it.translateToObjCExportStub() } + .flatMap { it.translateToObjCExportStub() } if (classKind == KtClassKind.ENUM_CLASS) { this += translateEnumMembers() diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCExportStub.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCExportStub.kt index ce2e74fd9bc..18be1c9db30 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCExportStub.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCExportStub.kt @@ -4,14 +4,24 @@ import org.jetbrains.kotlin.analysis.api.KtAnalysisSession import org.jetbrains.kotlin.analysis.api.symbols.* import org.jetbrains.kotlin.backend.konan.objcexport.ObjCClass import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportStub +import org.jetbrains.kotlin.utils.addIfNotNull context(KtAnalysisSession, KtObjCExportSession) -internal fun KtCallableSymbol.translateToObjCExportStub(): ObjCExportStub? { - return when (this) { - is KtPropertySymbol -> translateToObjCProperty() - is KtFunctionSymbol -> translateToObjCMethod() - else -> null +internal fun KtCallableSymbol.translateToObjCExportStub(): List { + val result = mutableListOf() + when (this) { + is KtPropertySymbol -> { + 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) diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCExtensionFacades.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCExtensionFacades.kt index 3c0981ae17a..bd3e904c5ff 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCExtensionFacades.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCExtensionFacades.kt @@ -64,7 +64,7 @@ fun KtResolvedObjCExportFile.translateToObjCExtensionFacades(): List ext.translateToObjCExportStub() }, + members = extensionSymbols.flatMap { ext -> ext.translateToObjCExportStub() }, categoryName = extensionsCategoryName, generics = emptyList(), superClass = null, 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 index cb81fcdfa23..fbc5a11a49e 100644 --- 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 @@ -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.symbols.* 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.objcexport.* import org.jetbrains.kotlin.name.Name @@ -20,10 +19,10 @@ internal val KtCallableSymbol.isConstructor: Boolean get() = this is KtConstructorSymbol context(KtAnalysisSession, KtObjCExportSession) -fun KtFunctionSymbol.translateToObjCMethod(): ObjCMethod? { +fun KtFunctionLikeSymbol.translateToObjCMethod(): ObjCMethod? { if (!isVisibleInObjC()) return null if (isFakeOverride) return null - if (isClone) return null + if (this is KtFunctionSymbol && isClone) return null return buildObjCMethod() } @@ -48,7 +47,7 @@ internal fun KtFunctionLikeSymbol.buildObjCMethod( val returnType: ObjCType = mapReturnType(bridge.returnBridge) val parameters = translateToObjCParameters(bridge) val selector = getSelector(bridge) - val selectors: List = splitSelector(selector) + val selectors = splitSelector(selector) val swiftName = getSwiftName(bridge) val attributes = mutableListOf() val returnBridge = bridge.returnBridge @@ -127,15 +126,18 @@ internal fun KtFunctionLikeSymbol.getSwiftName(methodBridge: MethodBridge): Stri append(getMangledName(forSwift = true)) append("(") - parameters@ for ((bridge, symbol) in parameters) { + parameters@ for ((bridge, parameter: KtObjCParameterData?) in parameters) { val label = when (bridge) { is MethodBridgeValueParameter.Mapped -> when { - //it is ReceiverParameterDescriptor -> it.getObjCName().asIdentifier(true) { "_" } + parameter?.isReceiver == true -> "_" method is KtPropertySetterSymbol -> when (parameters.size) { 1 -> "_" else -> "value" } - else -> symbol!!.name + else -> { + if (parameter == null) continue@parameters + else if (parameter.isReceiver) "_" else parameter.name + } } MethodBridgeValueParameter.ErrorOutParameter -> continue@parameters is MethodBridgeValueParameter.SuspendCompletion -> "completionHandler" @@ -149,7 +151,6 @@ internal fun KtFunctionLikeSymbol.getSwiftName(methodBridge: MethodBridge): Stri } return sb.toString() - } @@ -190,23 +191,23 @@ fun KtFunctionLikeSymbol.getSelector(methodBridge: MethodBridge): String { } val parameters = methodBridge.valueParametersAssociated(this) - val method = this - val sb = StringBuilder() sb.append(method.getMangledName(forSwift = false)) - parameters.forEachIndexed { index, (bridge, typeParameterSymbol) -> + parameters.forEachIndexed { index, (bridge, parameter) -> val name = when (bridge) { is MethodBridgeValueParameter.Mapped -> when { + parameter?.isReceiver == true -> "" method is KtPropertySetterSymbol -> when (parameters.size) { 1 -> "" else -> "value" } else -> { - typeParameterSymbol!!.name.toString() + if (parameter == null) return@forEachIndexed + else if (parameter.isReceiver) "" else parameter.name.toString() } } MethodBridgeValueParameter.ErrorOutParameter -> "error" @@ -255,26 +256,6 @@ private fun String.handleSpecialNames(prefix: String): String { return this } -/** - * [org.jetbrains.kotlin.backend.konan.objcexport.MethodBrideExtensionsKt.valueParametersAssociated] - */ -@InternalKotlinNativeApi -fun MethodBridge.valueParametersAssociated( - function: KtFunctionLikeSymbol, -): List> { - 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) && (this.length == words.length || !this[words.length].isLowerCase()) diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCObject.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCObject.kt index 7b6eeae6736..86bd1730a1e 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCObject.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCObject.kt @@ -33,7 +33,7 @@ fun KtClassOrObjectSymbol.translateToObjCObject(): ObjCClass? { objectMembers += getDefaultMembers() objectMembers += getDeclaredMemberScope().getCallableSymbols() .sortedWith(StableCallableOrder) - .mapNotNull { it.translateToObjCExportStub() } + .flatMap { it.translateToObjCExportStub() } return ObjCInterfaceImpl( name = name.objCName, diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCParameters.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCParameters.kt index c206dc3791f..3266ae30c12 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCParameters.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCParameters.kt @@ -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.KtPropertySetterSymbol 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.objcexport.* import org.jetbrains.kotlin.name.StandardClassIds -import org.jetbrains.kotlin.psi.KtParameter context(KtAnalysisSession, KtObjCExportSession) internal fun KtFunctionLikeSymbol.translateToObjCParameters(baseMethodBridge: MethodBridge): List { @@ -27,13 +25,19 @@ internal fun KtFunctionLikeSymbol.translateToObjCParameters(baseMethodBridge: Me val usedNames = mutableSetOf() - valueParametersAssociated.forEach { (bridge: MethodBridgeValueParameter, parameter: KtValueParameterSymbol?) -> - val candidateName: String = when (bridge) { + valueParametersAssociated.forEach { (bridge: MethodBridgeValueParameter, parameter: KtObjCParameterData?) -> + val candidateName = when (bridge) { is MethodBridgeValueParameter.Mapped -> { - if (parameter == null) throw IllegalStateException("Parameter shouldn't be null") + if (parameter == null) return@forEach when { - this is KtPropertySetterSymbol -> "value" - else -> parameter.name.toString() + this is KtPropertySetterSymbol -> { + if (parameter.isReceiver) "receiver" + else "value" + } + else -> { + if (parameter.isReceiver) "receiver" + else parameter.name.toString() + } } } MethodBridgeValueParameter.ErrorOutParameter -> "error" @@ -45,10 +49,10 @@ internal fun KtFunctionLikeSymbol.translateToObjCParameters(baseMethodBridge: Me val type = when (bridge) { is MethodBridgeValueParameter.Mapped -> { - val returnType = parameter!!.returnType + val returnType = parameter!!.type if (parameter.isVararg) { //vararg is a special case, [parameter.returnType] is T, we need Array - buildClassType(StandardClassIds.Array) { argument(parameter.returnType) }.translateToObjCType(bridge.bridge) + buildClassType(StandardClassIds.Array) { argument(parameter.type) }.translateToObjCType(bridge.bridge) } else { returnType.translateToObjCType(bridge.bridge) } 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 cc32983d96d..74d71aef156 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 @@ -28,7 +28,7 @@ fun KtClassOrObjectSymbol.translateToObjCProtocol(): ObjCProtocol? { val members = getCallableSymbolsForObjCMemberTranslation() .filter { it.isObjCBaseCallable() } .sortedWith(StableCallableOrder) - .mapNotNull { it.translateToObjCExportStub() } + .flatMap { it.translateToObjCExportStub() } val comment: ObjCComment? = annotationsList.translateToObjCComment() diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCTopLevelFacade.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCTopLevelFacade.kt index 7ba197d6506..9e75dc78f4c 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCTopLevelFacade.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCTopLevelFacade.kt @@ -60,7 +60,7 @@ fun KtResolvedObjCExportFile.translateToObjCTopLevelFacade(): ObjCInterface? { origin = null, attributes = listOf(OBJC_SUBCLASSING_RESTRICTED) + fileName.toNameAttributes(), superProtocols = emptyList(), - members = extensions.mapNotNull { it.translateToObjCExportStub() }, + members = extensions.flatMap { it.translateToObjCExportStub() }, categoryName = null, generics = emptyList(), superClass = getDefaultSuperClassOrProtocolName().objCName, diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translationOrder.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translationOrder.kt index a1c2011c980..d9aa9b4c9f3 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translationOrder.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translationOrder.kt @@ -6,13 +6,12 @@ package org.jetbrains.kotlin.objcexport import org.jetbrains.kotlin.analysis.api.symbols.* +import org.jetbrains.kotlin.analysis.api.symbols.markers.KtNamedSymbol internal val StableFileOrder: Comparator get() = compareBy { file -> file.packageFqName.asString() } .thenComparing { file -> file.fileName } -internal val StablePropertyOrder: Comparator = compareBy { it.name } - internal val StableFunctionOrder: Comparator get() = compareBy( { it.isConstructor }, @@ -42,6 +41,20 @@ internal val StableClassifierOrder: Comparator = 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) "" +// else "" +// } +// else -> it.name.toString() +// } +internal val StableNamedOrder: Comparator = compareBy { it.name.toString() } + internal val StableCallableOrder: Comparator = compareBy { when (it) { is KtConstructorSymbol -> 0 @@ -49,12 +62,11 @@ internal val StableCallableOrder: Comparator = compareBy 2 else -> 3 } -} - .thenComparing(StableConstructorOrder) - .thenComparing(StablePropertyOrder) +}.thenComparing(StableConstructorOrder) .thenComparing(StableFunctionOrder) + .thenComparing(StableNamedOrder) -private inline fun Comparator.thenComparing(comparator: Comparator): Comparator where R : T { +private inline fun Comparator.thenComparing(comparator: Comparator): Comparator { return thenComparing { a, b -> if (a is R && b is R) comparator.compare(a, b) else 0 } diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/valueParametersAssociated.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/valueParametersAssociated.kt new file mode 100644 index 00000000000..d3079acd970 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/valueParametersAssociated.kt @@ -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> { + + val result = mutableListOf>() + 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 + } + } diff --git a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/ObjCReceiverTypeTest.kt b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/ObjCReceiverTypeTest.kt new file mode 100644 index 00000000000..1df5a9781aa --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/ObjCReceiverTypeTest.kt @@ -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) + } + } +} \ No newline at end of file diff --git a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportHeaderGeneratorTest.kt b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportHeaderGeneratorTest.kt index bd2e41a8125..8c36b750cec 100644 --- a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportHeaderGeneratorTest.kt +++ b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportHeaderGeneratorTest.kt @@ -356,6 +356,21 @@ class ObjCExportHeaderGeneratorTest(private val generator: HeaderGenerator) { 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()) { if (!root.isDirectory) fail("Expected ${root.absolutePath} to be directory") val generatedHeaders = generator.generateHeaders(root, configuration).toString() diff --git a/native/objcexport-header-generator/testData/headers/innerClass/!innerClass.h b/native/objcexport-header-generator/testData/headers/innerClass/!innerClass.h new file mode 100644 index 00000000000..46b320115fe --- /dev/null +++ b/native/objcexport-header-generator/testData/headers/innerClass/!innerClass.h @@ -0,0 +1,37 @@ +#import +#import +#import +#import +#import +#import +#import + +@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 diff --git a/native/objcexport-header-generator/testData/headers/innerClass/Foo.kt b/native/objcexport-header-generator/testData/headers/innerClass/Foo.kt new file mode 100644 index 00000000000..b808f01dd2b --- /dev/null +++ b/native/objcexport-header-generator/testData/headers/innerClass/Foo.kt @@ -0,0 +1,3 @@ +class Foo { + inner class Inner +} \ No newline at end of file diff --git a/native/objcexport-header-generator/testData/headers/innerClassWithExtensionFunction/!innerClassWithExtensionFunction.h b/native/objcexport-header-generator/testData/headers/innerClassWithExtensionFunction/!innerClassWithExtensionFunction.h new file mode 100644 index 00000000000..308734c44df --- /dev/null +++ b/native/objcexport-header-generator/testData/headers/innerClassWithExtensionFunction/!innerClassWithExtensionFunction.h @@ -0,0 +1,41 @@ +#import +#import +#import +#import +#import +#import +#import + +@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 diff --git a/native/objcexport-header-generator/testData/headers/innerClassWithExtensionFunction/Foo.kt b/native/objcexport-header-generator/testData/headers/innerClassWithExtensionFunction/Foo.kt new file mode 100644 index 00000000000..58203519a84 --- /dev/null +++ b/native/objcexport-header-generator/testData/headers/innerClassWithExtensionFunction/Foo.kt @@ -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) {} + } +} \ No newline at end of file