diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getDefaultSuperClassOrProtocolName.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getDefaultSuperClassOrProtocolName.kt new file mode 100644 index 00000000000..5e2eb40804c --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getDefaultSuperClassOrProtocolName.kt @@ -0,0 +1,17 @@ +package org.jetbrains.kotlin.objcexport.analysisApiUtils + +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportClassOrProtocolName +import org.jetbrains.kotlin.objcexport.KtObjCExportSession +import org.jetbrains.kotlin.objcexport.getObjCKotlinStdlibClassOrProtocolName + +/** + * Some entities like top level functions are wrapped into classes with Base super class. + * + * @interface FooKt : Base + * + (NSString *)myTopLevelFunction __attribute__((swift_name("myTopLevelFunction()"))); + * @end + */ +context(KtObjCExportSession) +internal fun getDefaultSuperClassOrProtocolName(): ObjCExportClassOrProtocolName { + return "Base".getObjCKotlinStdlibClassOrProtocolName() +} \ No newline at end of file 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 new file mode 100644 index 00000000000..e6f0a64477e --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/getMethodBridge.kt @@ -0,0 +1,172 @@ +package org.jetbrains.kotlin.objcexport.analysisApiUtils + +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionLikeSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionSymbol +import org.jetbrains.kotlin.analysis.api.types.KtFunctionalType +import org.jetbrains.kotlin.analysis.api.types.KtType +import org.jetbrains.kotlin.backend.konan.KonanPrimitiveType +import org.jetbrains.kotlin.backend.konan.objcexport.* +import org.jetbrains.kotlin.objcexport.* + +/** + * [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportMapperKt.bridgeMethodImpl] + */ +context(KtAnalysisSession, KtObjCExportSession) +internal fun KtFunctionLikeSymbol.getMethodBridge(): MethodBridge { + + val valueParameters = mutableListOf() + + val receiver = if (isConstructor && isArray) { + MethodBridgeReceiver.Factory + } else if (isTopLevel) { + MethodBridgeReceiver.Static + } else { + MethodBridgeReceiver.Instance + } + + this.valueParameters.forEach { + valueParameters += bridgeParameter(it.returnType) + } + + if (this is KtFunctionSymbol && isSuspend) { + valueParameters += MethodBridgeValueParameter.SuspendCompletion(false) + } + + return MethodBridge( + bridgeReturnType(), + receiver, + valueParameters + ) +} + +/** + * [ObjCExportMapper.bridgeParameter] + */ +context(KtAnalysisSession, KtObjCExportSession) +private fun bridgeParameter(type: KtType): MethodBridgeValueParameter { + return MethodBridgeValueParameter.Mapped(bridgeType(type)) +} + +/** + * [ObjCExportMapper.bridgeType] + */ +context(KtAnalysisSession) +private fun bridgeType( + type: KtType, +): TypeBridge { + + return if (type.isPrimitive) { + val objCType = when { + type.isBoolean -> ObjCValueType.BOOL + type.isChar -> ObjCValueType.UNICHAR + type.isByte -> ObjCValueType.CHAR + type.isShort -> ObjCValueType.SHORT + type.isInt -> ObjCValueType.INT + type.isLong -> ObjCValueType.LONG_LONG + type.isFloat -> ObjCValueType.FLOAT + type.isDouble -> ObjCValueType.DOUBLE + type.isUByte -> ObjCValueType.UNSIGNED_CHAR + type.isUShort -> ObjCValueType.UNSIGNED_SHORT + type.isUInt -> ObjCValueType.UNSIGNED_INT + type.isULong -> ObjCValueType.UNSIGNED_LONG_LONG + else -> + /** + * Handle [KonanPrimitiveType.NON_NULL_NATIVE_PTR] and [KonanPrimitiveType.VECTOR128] + */ + TODO() + } + ValueTypeBridge(objCType) + } else if (type.isFunctionType) { + bridgeFunctionType(type) + } else { + ReferenceBridge + } +} + +/** + * [ObjCExportMapper.bridgeFunctionType] + */ +context(KtAnalysisSession) +private fun bridgeFunctionType(type: KtType): TypeBridge { + + val numberOfParameters: Int + val returnType: KtType + + if (type is KtFunctionalType) { + numberOfParameters = type.parameterTypes.size + returnType = type.parameterTypes.last() + } else { + numberOfParameters = 0 + returnType = type + } + + val returnsVoid = returnType.isUnit || returnType.isNothing + return BlockPointerBridge(numberOfParameters, returnsVoid) +} + +/** + * [ObjCExportMapper.bridgeReturnType] + */ +context(KtAnalysisSession, KtObjCExportSession) +private fun KtCallableSymbol.bridgeReturnType(): MethodBridge.ReturnValue { + + val convertExceptionsToErrors = false // TODO: Add exception handling and return MethodBridge.ReturnValue.WithError.ZeroForError + + if (isArray) { + return MethodBridge.ReturnValue.Instance.FactoryResult + } else if (isConstructor) { + val result = MethodBridge.ReturnValue.Instance.InitResult + if (convertExceptionsToErrors) { + MethodBridge.ReturnValue.WithError.ZeroForError(result, successMayBeZero = false) + } else { + return result + } + } else if (returnType.isSuspendFunctionType) { + return MethodBridge.ReturnValue.Suspend + } + + //TODO: handle hashCode +// descriptor.containingDeclaration.let { it is ClassDescriptor && KotlinBuiltIns.isAny(it) } && +// descriptor.name.asString() == "hashCode" -> { +// assert(!convertExceptionsToErrors) +// MethodBridge.ReturnValue.HashCode +// } + + //TODO: handle getter +// descriptor is PropertyGetterDescriptor -> { +// assert(!convertExceptionsToErrors) +// MethodBridge.ReturnValue.Mapped(bridgePropertyType(descriptor.correspondingProperty)) +// } + + if (returnType.isUnit || returnType.isNothing) { + return if (convertExceptionsToErrors) { + MethodBridge.ReturnValue.WithError.Success + } else { + MethodBridge.ReturnValue.Void + } + } + + + val returnTypeBridge = bridgeType(returnType) + val successReturnValueBridge = MethodBridge.ReturnValue.Mapped(returnTypeBridge) + + return if (convertExceptionsToErrors) { + val canReturnZero = !returnTypeBridge.isReferenceOrPointer() || returnType.canBeNull + MethodBridge.ReturnValue.WithError.ZeroForError( + successReturnValueBridge, + successMayBeZero = canReturnZero + ) + } else { + successReturnValueBridge + } +} + +/** + * [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportMapperKt.isReferenceOrPointer] + */ +private fun TypeBridge.isReferenceOrPointer(): Boolean = when (this) { + ReferenceBridge, is BlockPointerBridge -> true + is ValueTypeBridge -> this.objCValueType == ObjCValueType.POINTER +} diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/isTopLevel.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/isTopLevel.kt new file mode 100644 index 00000000000..b9f5abd60ef --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/analysisApiUtils/isTopLevel.kt @@ -0,0 +1,6 @@ +package org.jetbrains.kotlin.objcexport.analysisApiUtils + +import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol + +internal val KtCallableSymbol.isTopLevel: Boolean + get() = callableIdIfNonLocal?.classId == null 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 index a7019747fe3..5f650893230 100644 --- 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 @@ -2,9 +2,8 @@ package org.jetbrains.kotlin.objcexport.analysisApiUtils import org.jetbrains.kotlin.analysis.api.KtAnalysisSession import org.jetbrains.kotlin.analysis.api.symbols.* -import org.jetbrains.kotlin.objcexport.KtObjCExportSession -context(KtAnalysisSession, KtObjCExportSession) +context(KtAnalysisSession) internal fun KtClassOrObjectSymbol.members(): List { return getMemberScope() .getAllSymbols() diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/attributes.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/attributes.kt new file mode 100644 index 00000000000..cda49b87b12 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/attributes.kt @@ -0,0 +1,3 @@ +package org.jetbrains.kotlin.objcexport + +internal const val OBJC_SUBCLASSING_RESTRICTED = "objc_subclassing_restricted" \ No newline at end of file diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getObjCFileName.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getObjCFileName.kt new file mode 100644 index 00000000000..e986dd6d3e6 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/getObjCFileName.kt @@ -0,0 +1,12 @@ +package org.jetbrains.kotlin.objcexport + +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportFileName + + +context(KtObjCExportSession) +internal fun String.getObjCFileName(): ObjCExportFileName { + return ObjCExportFileName( + swiftName = this, + objCName = "${configuration.frameworkName.orEmpty()}$this" + ) +} 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 fdee01cad3e..bcc77fe32f7 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 @@ -9,6 +9,7 @@ import org.jetbrains.kotlin.backend.konan.KonanFqNames import org.jetbrains.kotlin.backend.konan.objcexport.* import org.jetbrains.kotlin.builtins.StandardNames import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.objcexport.analysisApiUtils.getDefaultSuperClassOrProtocolName import org.jetbrains.kotlin.objcexport.analysisApiUtils.getSuperClassSymbolNotAny import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC import org.jetbrains.kotlin.objcexport.analysisApiUtils.members @@ -19,7 +20,7 @@ fun KtClassOrObjectSymbol.translateToObjCClass(): ObjCClass? { if (!isVisibleInObjC()) return null val superClass = getSuperClassSymbolNotAny() - val kotlinAnyName = "Base".getObjCKotlinStdlibClassOrProtocolName() + val kotlinAnyName = getDefaultSuperClassOrProtocolName() val superName = if (superClass == null) kotlinAnyName else throw RuntimeException("Super class translation isn't implemented yet") val enumKind = this.classKind == KtClassKind.ENUM_CLASS val final = if (this is KtSymbolWithModality) this.modality == Modality.FINAL else false @@ -48,8 +49,6 @@ fun KtClassOrObjectSymbol.translateToObjCClass(): ObjCClass? { ) } -private const val OBJC_SUBCLASSING_RESTRICTED = "objc_subclassing_restricted" - private fun abbreviate(name: String): String { val normalizedName = name .replaceFirstChar(Char::uppercaseChar) @@ -72,4 +71,4 @@ private val mustBeDocumentedAnnotationsStopList = setOf( StandardNames.FqNames.deprecated, KonanFqNames.objCName, KonanFqNames.shouldRefineInSwift -) \ No newline at end of file +) 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 b3ba6caff01..bbabfbabc69 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 @@ -6,24 +6,23 @@ package org.jetbrains.kotlin.objcexport import org.jetbrains.kotlin.analysis.api.KtAnalysisSession -import org.jetbrains.kotlin.analysis.api.scopes.KtScope import org.jetbrains.kotlin.analysis.api.symbols.* import org.jetbrains.kotlin.analysis.api.symbols.KtClassKind.CLASS import org.jetbrains.kotlin.analysis.api.symbols.KtClassKind.INTERFACE import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportStub import org.jetbrains.kotlin.backend.konan.objcexport.ObjCHeader import org.jetbrains.kotlin.backend.konan.objcexport.ObjCProtocol +import org.jetbrains.kotlin.psi.KtFile + + context(KtAnalysisSession, KtObjCExportSession) -fun KtScope.translateToObjCHeader(): ObjCHeader { - val declarationsInScope = getAllSymbols() - .flatMap { symbol -> symbol.translateToObjCExportStubs() } - .toList() - +fun translateToObjCHeader(files: List) : ObjCHeader { + val declarations = files.flatMap { ktFile -> ktFile.translateToObjCExportStubs() } return ObjCHeader( - stubs = declarationsInScope, + stubs = declarations, classForwardDeclarations = emptySet(), - protocolForwardDeclarations = declarationsInScope + protocolForwardDeclarations = declarations .filterIsInstance() .flatMap { it.superProtocols } .toSet(), @@ -32,9 +31,22 @@ fun KtScope.translateToObjCHeader(): ObjCHeader { ) } +context(KtAnalysisSession, KtObjCExportSession) +fun KtFile.translateToObjCExportStubs(): List { + return this.getFileSymbol().translateToObjCExportStubs() +} + +context(KtAnalysisSession, KtObjCExportSession) +fun KtFileSymbol.translateToObjCExportStubs(): List { + return listOfNotNull(translateToObjCTopLevelInterfaceFileFacade()) + getFileScope().getClassifierSymbols() + .flatMap { classifierSymbol -> classifierSymbol.translateToObjCExportStubs() } +} + + context(KtAnalysisSession, KtObjCExportSession) internal fun KtSymbol.translateToObjCExportStubs(): List { return when { + this is KtFileSymbol -> translateToObjCExportStubs() this is KtClassOrObjectSymbol && classKind == INTERFACE -> listOfNotNull(translateToObjCProtocol()) this is KtClassOrObjectSymbol && classKind == CLASS -> listOfNotNull(translateToObjCClass()) this is KtConstructorSymbol -> translateToObjCConstructors() 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 1ca7f7e9419..7ad47a51d1e 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 @@ -2,25 +2,26 @@ package org.jetbrains.kotlin.objcexport +import com.intellij.openapi.util.io.FileUtil import org.jetbrains.kotlin.analysis.api.KtAnalysisSession import org.jetbrains.kotlin.analysis.api.annotations.KtAnnotationApplicationWithArgumentsInfo import org.jetbrains.kotlin.analysis.api.annotations.annotationInfos import org.jetbrains.kotlin.analysis.api.annotations.annotations import org.jetbrains.kotlin.analysis.api.symbols.* -import org.jetbrains.kotlin.analysis.api.types.KtFunctionalType import org.jetbrains.kotlin.analysis.api.types.KtType import org.jetbrains.kotlin.backend.konan.InternalKotlinNativeApi import org.jetbrains.kotlin.backend.konan.KonanFqNames -import org.jetbrains.kotlin.backend.konan.KonanPrimitiveType -import org.jetbrains.kotlin.backend.konan.cKeywords import org.jetbrains.kotlin.backend.konan.objcexport.* import org.jetbrains.kotlin.builtins.StandardNames import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.descriptors.Visibility import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.name.NameUtils import org.jetbrains.kotlin.objcexport.Predefined.anyMethodSelectors +import org.jetbrains.kotlin.objcexport.analysisApiUtils.getMethodBridge import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC +import org.jetbrains.kotlin.psi.KtFile internal val KtCallableSymbol.isConstructor: Boolean get() = this is KtConstructorSymbol @@ -29,27 +30,39 @@ internal val KtCallableSymbol.isArray: Boolean get() = false //TODO: temp k2 workaround context(KtAnalysisSession, KtObjCExportSession) -fun KtFunctionSymbol.translateToObjCMethod(): ObjCMethod? { +fun KtFunctionSymbol.translateToObjCMethod( +): ObjCMethod? { if (!isVisibleInObjC()) return null if (anyMethodSelectors.containsKey(this.name)) return null //temp, find replacement for org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.isReal + return buildObjCMethod() } + +context(KtAnalysisSession, KtObjCExportSession) +fun KtFileSymbol.getObjCFileClassOrProtocolName(): ObjCExportFileName? { + val ktFile = this.psi as? KtFile ?: return null + val name = NameUtils.getPackagePartClassNamePrefix(FileUtil.getNameWithoutExtension(ktFile.name)) + "Kt" + return name.toIdentifier().getObjCFileName() +} + /** * [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.buildMethod] */ context(KtAnalysisSession, KtObjCExportSession) -internal fun KtFunctionLikeSymbol.buildObjCMethod(unavailable: Boolean = false): ObjCMethod { - val baseMethodBridge = bridgeMethodImpl() +internal fun KtFunctionLikeSymbol.buildObjCMethod( + unavailable: Boolean = false, +): ObjCMethod { + val bridge = getMethodBridge() val origin: ObjCExportStubOrigin? = null - val returnType: ObjCType = mapReturnType(baseMethodBridge.returnBridge) - val parameters = collectParameters(baseMethodBridge, this) - val selector = getSelector() + val returnType: ObjCType = mapReturnType(bridge.returnBridge) + val parameters = translateToObjCParameters(bridge) + val selector = getSelector(bridge) val selectors: List = splitSelector(selector) - val swiftName = getSwiftName() + val swiftName = getSwiftName(bridge) val attributes = mutableListOf() - val returnBridge = baseMethodBridge.returnBridge - val comment = buildComment(this, baseMethodBridge, parameters) + val returnBridge = bridge.returnBridge + val comment = buildComment(this, bridge, parameters) attributes += getSwiftPrivateAttribute() ?: swiftNameAttribute(swiftName) @@ -74,78 +87,16 @@ internal fun KtFunctionLikeSymbol.buildObjCMethod(unavailable: Boolean = false): } return ObjCMethod( - comment, - origin, - true, - returnType, - selectors, - parameters, - attributes + comment = comment, + origin = origin, + isInstanceMethod = bridge.isInstance || isConstructor, + returnType = returnType, + selectors = selectors, + parameters = parameters, + attributes = attributes ) } -context(KtAnalysisSession, KtObjCExportSession) -private fun collectParameters(baseMethodBridge: MethodBridge, method: KtFunctionLikeSymbol): List { - 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] @@ -269,12 +220,12 @@ internal fun KtCallableSymbol.isRefinedInSwift(): Boolean = when { } context(KtAnalysisSession, KtObjCExportSession) -internal fun KtFunctionLikeSymbol.getSwiftName(): String { +internal fun KtFunctionLikeSymbol.getSwiftName(methodBridge: MethodBridge): String { //assert(mapper.isBaseMethod(method)) //TODO: implement isBaseMethod getPredefined(this, Predefined.anyMethodSwiftNames)?.let { return it } - val parameters = bridgeMethodImpl().valueParametersAssociated(this) + val parameters = methodBridge.valueParametersAssociated(this) val method = this val sb = StringBuilder().apply { @@ -343,11 +294,11 @@ private fun getPredefined(method: KtFunctionLikeSymbol, predefinedForA * [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamerImpl.getSelector] */ context(KtAnalysisSession, KtObjCExportSession) -fun KtFunctionLikeSymbol.getSelector(): String { +fun KtFunctionLikeSymbol.getSelector(methodBridge: MethodBridge): String { getPredefined(this, Predefined.anyMethodSelectors)?.let { return it } - val parameters = bridgeMethodImpl().valueParametersAssociated(this) + val parameters = methodBridge.valueParametersAssociated(this) val method = this @@ -389,35 +340,6 @@ fun KtFunctionLikeSymbol.getSelector(): String { return sb.toString() } -/** - * Not implemented - */ -private fun KtTypeParameterSymbol.getObjCName(): ObjCExportName { - TODO() -} - -///** -// * [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamerKt.toIdentifier] -// */ -//private fun String.toIdentifier(): String = this.toValidObjCSwiftIdentifier() - -///** -// * [org.jetbrains.kotlin.backend.konan.objcexport.ObjCName] -// */ -//private class ObjCName( -// private val kotlinName: String, -// private val objCName: String?, -// private val swiftName: String?, -// val isExact: Boolean, -//) { -// // TODO: Prevent mangling when objCName or swiftName is provided -// -// fun asString(forSwift: Boolean): String = swiftName.takeIf { forSwift } ?: objCName ?: kotlinName -// -// fun asIdentifier(forSwift: Boolean, default: (String) -> String = { it.toIdentifier() }): String = -// swiftName.takeIf { forSwift } ?: objCName ?: default(kotlinName) -//} - context(KtAnalysisSession, KtObjCExportSession) private fun KtFunctionLikeSymbol.getMangledName(forSwift: Boolean): String { @@ -466,7 +388,7 @@ private fun String.mangleIfSpecialFamily(prefix: String): String { * [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportNamerImpl.startsWithWords] */ private fun String.startsWithWords(words: String) = this.startsWith(words) && - (this.length == words.length || !this[words.length].isLowerCase()) + (this.length == words.length || !this[words.length].isLowerCase()) @InternalKotlinNativeApi fun MethodBridge.valueParametersAssociated( @@ -513,7 +435,7 @@ fun KtFunctionLikeSymbol.mapReturnType(returnBridge: MethodBridge.ReturnValue): if (!returnBridge.successMayBeZero) { check( successReturnType is ObjCNonNullReferenceType - || (successReturnType is ObjCPointerType && !successReturnType.nullable) + || (successReturnType is ObjCPointerType && !successReturnType.nullable) ) { "Unexpected return type: $successReturnType in $this" } @@ -528,44 +450,6 @@ fun KtFunctionLikeSymbol.mapReturnType(returnBridge: MethodBridge.ReturnValue): } } -/** - * [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportMapperKt.bridgeMethodImpl] - */ -context(KtAnalysisSession, KtObjCExportSession) -internal fun KtFunctionLikeSymbol.bridgeMethodImpl(): MethodBridge { - val isTopLevel = false //TODO implement and use ObjCExportMapper.isTopLevel - val valueParameters = mutableListOf() - - val receiver = if (isConstructor && isArray) { - MethodBridgeReceiver.Factory - } else if (isTopLevel) { - MethodBridgeReceiver.Static - } else { - MethodBridgeReceiver.Instance - } - - this.valueParameters.forEach { - valueParameters += bridgeParameter(it.returnType) - } - - if (this is KtFunctionSymbol && isSuspend) { - valueParameters += MethodBridgeValueParameter.SuspendCompletion(false) - } - - return MethodBridge( - bridgeReturnType(), - receiver, - valueParameters - ) -} - -/** - * [ObjCExportMapper.bridgeParameter] - */ -context(KtAnalysisSession, KtObjCExportSession) -private fun bridgeParameter(type: KtType): MethodBridgeValueParameter { - return MethodBridgeValueParameter.Mapped(bridgeType(type)) -} /** * [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportTranslatorImpl.mapType] @@ -599,125 +483,3 @@ private fun KtType.mapType(typeBridge: TypeBridge): ObjCType { } } } - -/** - * [org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportMapperKt.isReferenceOrPointer] - */ -private fun TypeBridge.isReferenceOrPointer(): Boolean = when (this) { - ReferenceBridge, is BlockPointerBridge -> true - is ValueTypeBridge -> this.objCValueType == ObjCValueType.POINTER -} - -/** - * [ObjCExportMapper.bridgeFunctionType] - */ -context(KtAnalysisSession) -private fun bridgeFunctionType(type: KtType): TypeBridge { - - val numberOfParameters: Int - val returnType: KtType - - if (type is KtFunctionalType) { - numberOfParameters = type.parameterTypes.size - returnType = type.parameterTypes.last() - } else { - numberOfParameters = 0 - returnType = type - } - - val returnsVoid = returnType.isUnit || returnType.isNothing - return BlockPointerBridge(numberOfParameters, returnsVoid) -} - -/** - * [ObjCExportMapper.bridgeType] - */ -context(KtAnalysisSession) -private fun bridgeType( - type: KtType, -): TypeBridge { - - return if (type.isPrimitive) { - val objCType = when { - type.isBoolean -> ObjCValueType.BOOL - type.isChar -> ObjCValueType.UNICHAR - type.isByte -> ObjCValueType.CHAR - type.isShort -> ObjCValueType.SHORT - type.isInt -> ObjCValueType.INT - type.isLong -> ObjCValueType.LONG_LONG - type.isFloat -> ObjCValueType.FLOAT - type.isDouble -> ObjCValueType.DOUBLE - type.isUByte -> ObjCValueType.UNSIGNED_CHAR - type.isUShort -> ObjCValueType.UNSIGNED_SHORT - type.isUInt -> ObjCValueType.UNSIGNED_INT - type.isULong -> ObjCValueType.UNSIGNED_LONG_LONG - else -> - /** - * Handle [KonanPrimitiveType.NON_NULL_NATIVE_PTR] and [KonanPrimitiveType.VECTOR128] - */ - TODO() - } - ValueTypeBridge(objCType) - } else if (type.isFunctionType) { - bridgeFunctionType(type) - } else { - ReferenceBridge - } -} - -/** - * [ObjCExportMapper.bridgeReturnType] - */ -context(KtAnalysisSession, KtObjCExportSession) -internal fun KtCallableSymbol.bridgeReturnType(): MethodBridge.ReturnValue { - - val convertExceptionsToErrors = false // TODO: Add exception handling and return MethodBridge.ReturnValue.WithError.ZeroForError - - if (isArray) { - return MethodBridge.ReturnValue.Instance.FactoryResult - } else if (isConstructor) { - val result = MethodBridge.ReturnValue.Instance.InitResult - if (convertExceptionsToErrors) { - MethodBridge.ReturnValue.WithError.ZeroForError(result, successMayBeZero = false) - } else { - return result - } - } else if (returnType.isSuspendFunctionType) { - return MethodBridge.ReturnValue.Suspend - } - - //TODO: handle hashCode -// descriptor.containingDeclaration.let { it is ClassDescriptor && KotlinBuiltIns.isAny(it) } && -// descriptor.name.asString() == "hashCode" -> { -// assert(!convertExceptionsToErrors) -// MethodBridge.ReturnValue.HashCode -// } - - //TODO: handle getter -// descriptor is PropertyGetterDescriptor -> { -// assert(!convertExceptionsToErrors) -// MethodBridge.ReturnValue.Mapped(bridgePropertyType(descriptor.correspondingProperty)) -// } - - if (returnType.isUnit || returnType.isNothing) { - return if (convertExceptionsToErrors) { - MethodBridge.ReturnValue.WithError.Success - } else { - MethodBridge.ReturnValue.Void - } - } - - - val returnTypeBridge = bridgeType(returnType) - val successReturnValueBridge = MethodBridge.ReturnValue.Mapped(returnTypeBridge) - - return if (convertExceptionsToErrors) { - val canReturnZero = !returnTypeBridge.isReferenceOrPointer() || returnType.canBeNull - MethodBridge.ReturnValue.WithError.ZeroForError( - successReturnValueBridge, - successMayBeZero = canReturnZero - ) - } else { - successReturnValueBridge - } -} \ No newline at end of file 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 new file mode 100644 index 00000000000..b6fe548fa2b --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCParameters.kt @@ -0,0 +1,78 @@ +package org.jetbrains.kotlin.objcexport + +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionLikeSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySetterSymbol +import org.jetbrains.kotlin.analysis.api.symbols.KtTypeParameterSymbol +import org.jetbrains.kotlin.backend.konan.cKeywords +import org.jetbrains.kotlin.backend.konan.objcexport.* + +context(KtAnalysisSession, KtObjCExportSession) +internal fun KtFunctionLikeSymbol.translateToObjCParameters(baseMethodBridge: MethodBridge): List { + 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(this) + + 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 { + this is KtPropertySetterSymbol -> "value" + else -> parameter.getObjCName().name(false) + } + } + MethodBridgeValueParameter.ErrorOutParameter -> "error" + is MethodBridgeValueParameter.SuspendCompletion -> "completionHandler" + } + + val uniqueName = unifyName(candidateName, usedNames) + usedNames += uniqueName + + val type = when (bridge) { + is MethodBridgeValueParameter.Mapped -> TODO("Fetch KtType from KtTypeParameterSymbol: $parameter") + MethodBridgeValueParameter.ErrorOutParameter -> + ObjCPointerType(ObjCNullableReferenceType(ObjCClassType("NSError")), nullable = true) + + is MethodBridgeValueParameter.SuspendCompletion -> { + val resultType = if (bridge.useUnitCompletion) { + null + } else { + when (val it = this.returnType.translateToObjCReferenceType()) { + is ObjCNonNullReferenceType -> ObjCNullableReferenceType(it, isNullableResult = false) + is ObjCNullableReferenceType -> ObjCNullableReferenceType(it.nonNullType, isNullableResult = true) + } + } + ObjCBlockPointerType( + returnType = ObjCVoidType, + parameterTypes = listOfNotNull( + resultType, + ObjCNullableReferenceType(ObjCClassType("NSError")) + ) + ) + } + } + + val origin: ObjCExportStubOrigin? = null + val todo: Nothing? = null + parameters += ObjCParameter(uniqueName, origin, type, todo) + } + return parameters +} + +/** + * Not implemented + */ +internal fun KtTypeParameterSymbol.getObjCName(): ObjCExportName { + TODO() +} \ 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 306ef87944b..688fd113c9d 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 @@ -9,6 +9,7 @@ import org.jetbrains.kotlin.analysis.api.KtAnalysisSession import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySymbol import org.jetbrains.kotlin.backend.konan.objcexport.ObjCProperty import org.jetbrains.kotlin.backend.konan.objcexport.swiftNameAttribute +import org.jetbrains.kotlin.objcexport.analysisApiUtils.getMethodBridge import org.jetbrains.kotlin.objcexport.analysisApiUtils.isVisibleInObjC context(KtAnalysisSession, KtObjCExportSession) @@ -24,12 +25,12 @@ context(KtAnalysisSession, KtObjCExportSession) fun KtPropertySymbol.buildProperty(): ObjCProperty { val propertyName = getObjCPropertyName() val name = propertyName.objCName - val getterBridge = getter?.bridgeMethodImpl() + val getterBridge = getter?.getMethodBridge() val type = getter?.mapReturnType(getterBridge!!.returnBridge) val attributes = mutableListOf() val setterName: String? - if (!getterBridge!!.isInstance) { + if (getterBridge!!.isInstance) { attributes += "class" } @@ -38,7 +39,7 @@ fun KtPropertySymbol.buildProperty(): ObjCProperty { val shouldBeSetterExposed = true //TODO: mapper.shouldBeExposed if (propertySetter != null && shouldBeSetterExposed) { - val setterSelector = propertySetter.getSelector() + val setterSelector = propertySetter.getSelector(getterBridge) setterName = if (setterSelector != "set" + name.replaceFirstChar(Char::uppercaseChar) + ":") setterSelector else null } else { attributes += "readonly" diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCTopLevelInterfaceFileFacade.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCTopLevelInterfaceFileFacade.kt new file mode 100644 index 00000000000..dab932617c6 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCTopLevelInterfaceFileFacade.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.objcexport + +import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.symbols.KtFileSymbol +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCInterface +import org.jetbrains.kotlin.backend.konan.objcexport.ObjCInterfaceImpl +import org.jetbrains.kotlin.objcexport.analysisApiUtils.getDefaultSuperClassOrProtocolName + +/** + * Translates top level functions/properties inside the given [this] file as a single [ObjCInterface]. + * ## example: + * given a file "Foo.kt" + * + * ```kotlin + * + * fun myTopLevelFunction() = 42 + * + * val myTopLevelProperty get() = 42 + * + * class Bar { + * fun bar() = Unit + * } + * + * ``` + * + * This will be exporting two Interfaces: + * + * ``` + * FooKt: Base + * - myTopLevelFunction + * - myTopLevelProperty + * + * Bar + * - bar + * ``` + * + * Where `FooKt` would be the "top level interface file facade" returned by this function. + */ +context(KtAnalysisSession, KtObjCExportSession) +fun KtFileSymbol.translateToObjCTopLevelInterfaceFileFacade(): ObjCInterface? { + val topLevelCallableStubs = getFileScope().getCallableSymbols() + .flatMap { callableSymbol -> callableSymbol.translateToObjCExportStubs() } + .toList() + /* If there are no top level functions or properties, we do not need to export a file facade */ + .ifEmpty { return null } + + val fileName = getObjCFileClassOrProtocolName() + ?: throw IllegalStateException("Top level file '$this' cannot be translated without file name") + + val name = fileName.objCName + val attributes = listOf(OBJC_SUBCLASSING_RESTRICTED) + + val superClass = getDefaultSuperClassOrProtocolName() + + return ObjCInterfaceImpl( + name = name, + comment = null, + origin = null, + attributes = attributes, + superProtocols = emptyList(), + members = topLevelCallableStubs, + categoryName = null, + generics = emptyList(), + superClass = superClass.objCName, + superClassGenerics = emptyList() + ) +} diff --git a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/testUtils/AnalysisApiAssertions.kt b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/testUtils/AnalysisApiAssertions.kt index 008b4f16ce6..c4a55b4c441 100644 --- a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/testUtils/AnalysisApiAssertions.kt +++ b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/testUtils/AnalysisApiAssertions.kt @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.objcexport.testUtils import org.jetbrains.kotlin.analysis.api.KtAnalysisSession import org.jetbrains.kotlin.analysis.api.scopes.KtScope +import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionSymbol import org.jetbrains.kotlin.analysis.api.symbols.KtNamedClassOrObjectSymbol import org.jetbrains.kotlin.name.Name @@ -43,3 +44,7 @@ fun KtScope.getFunctionOrFail(name: String): KtFunctionSymbol { return symbol } +context(KtAnalysisSession) +fun KtClassOrObjectSymbol.getFunctionOrFail(name: String) : KtFunctionSymbol { + return this.getMemberScope().getFunctionOrFail(name) +} diff --git a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/testUtils/AnalysisApiHeaderGenerator.kt b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/testUtils/AnalysisApiHeaderGenerator.kt index c34f256dd11..469bafc315f 100644 --- a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/testUtils/AnalysisApiHeaderGenerator.kt +++ b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/testUtils/AnalysisApiHeaderGenerator.kt @@ -31,10 +31,8 @@ object AnalysisApiHeaderGenerator : HeaderGenerator { val session = createStandaloneAnalysisApiSession(root.listFiles().orEmpty().filter { it.extension == "kt" }) val (module, files) = session.modulesWithFiles.entries.single() return analyze(module) { - val scope = files.map { it as KtFile }.map { it.getFileSymbol().getFileScope() }.asCompositeScope() - KtObjCExportSession(KtObjCExportConfiguration()) { - scope.translateToObjCHeader().toString() + translateToObjCHeader(files.map { it as KtFile }).toString() } } } diff --git a/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/IsTopLevelTests.kt b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/IsTopLevelTests.kt new file mode 100644 index 00000000000..18860b118f6 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/test/org/jetbrains/kotlin/objcexport/tests/IsTopLevelTests.kt @@ -0,0 +1,31 @@ +package org.jetbrains.kotlin.objcexport.tests + +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.objcexport.analysisApiUtils.isTopLevel +import org.jetbrains.kotlin.objcexport.testUtils.InlineSourceCodeAnalysis +import org.jetbrains.kotlin.objcexport.testUtils.getClassOrFail +import org.jetbrains.kotlin.objcexport.testUtils.getFunctionOrFail +import org.junit.jupiter.api.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class IsTopLevelTests( + private val inlineSourceCodeAnalysis: InlineSourceCodeAnalysis, +) { + @Test + fun `test - top level fun`() { + val ktFile = inlineSourceCodeAnalysis.createKtFile( + """ + fun topFun() {} + class TopClass { + fun classFun() {} + } + """.trimIndent() + ) + + analyze(ktFile) { + assertTrue(ktFile.getFunctionOrFail("topFun").isTopLevel) + assertFalse (ktFile.getClassOrFail("TopClass").getFunctionOrFail("classFun").isTopLevel) + } + } +} \ No newline at end of file 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 7481508742d..aa0b74c55f8 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 @@ -18,7 +18,9 @@ interface ObjCExportClassOrProtocolName : ObjCExportName { interface ObjCExportPropertyName : ObjCExportName -interface ObjCExportFunctionName: ObjCExportName +interface ObjCExportFunctionName : ObjCExportName + +interface ObjCExportFileName : ObjCExportName fun ObjCExportClassOrProtocolName( swiftName: String, @@ -34,7 +36,6 @@ private data class ObjCExportClassOrProtocolNameImpl( override val swiftName: String, override val objCName: String, override val binaryName: String, - ) : ObjCExportClassOrProtocolName fun ObjCExportPropertyName( @@ -53,6 +54,14 @@ fun ObjCExportFunctionName( objCName = objCName ) +fun ObjCExportFileName( + swiftName: String, + objCName: String, +): ObjCExportFileName = ObjCExportFileNameImpl( + swiftName = swiftName, + objCName = objCName +) + private data class ObjCExportPropertyNameImpl( override val swiftName: String, override val objCName: String, @@ -63,6 +72,11 @@ private data class ObjCExportFunctionNameImpl( override val objCName: String, ) : ObjCExportFunctionName +private data class ObjCExportFileNameImpl( + override val swiftName: String, + override val objCName: String, +) : ObjCExportFileName + fun ObjCExportClassOrProtocolName.toNameAttributes(): List = listOfNotNull( binaryName.takeIf { it != objCName }?.let { objcRuntimeNameAttribute(it) }, @@ -77,7 +91,8 @@ fun objcRuntimeNameAttribute(name: String) = "objc_runtime_name(\"$name\")" fun ObjCExportName.name(forSwift: Boolean) = swiftName.takeIf { forSwift } ?: objCName -private fun String.toIdentifier(): String = this.toValidObjCSwiftIdentifier() +@InternalKotlinNativeApi +fun String.toIdentifier(): String = this.toValidObjCSwiftIdentifier() internal fun String.toValidObjCSwiftIdentifier(): String { if (this.isEmpty()) return "__"