From 622a88ff267444638c6105a724542678697607cd Mon Sep 17 00:00:00 2001 From: Sebastian Sellmair Date: Tue, 20 Feb 2024 11:39:31 +0100 Subject: [PATCH] [ObjCExport] Translate ObjCObject types, respecting the @ExternalObjCClass annotation KT-65891 Fixed --- .../objcexport/translateToObjCObjectType.kt | 89 +++++++++++++++++++ .../kotlin/objcexport/translateToObjCType.kt | 2 +- .../tests/ObjCExportTypeTranslationTest.kt | 51 +++++++++++ 3 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCObjectType.kt diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCObjectType.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCObjectType.kt new file mode 100644 index 00000000000..ba69ebc6a52 --- /dev/null +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCObjectType.kt @@ -0,0 +1,89 @@ +/* + * 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.annotations.hasAnnotation +import org.jetbrains.kotlin.analysis.api.symbols.KtClassKind +import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol +import org.jetbrains.kotlin.analysis.api.symbols.nameOrAnonymous +import org.jetbrains.kotlin.analysis.api.types.KtNonErrorClassType +import org.jetbrains.kotlin.analysis.api.types.KtType +import org.jetbrains.kotlin.backend.konan.objcexport.* +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.name.NativeForwardDeclarationKind +import org.jetbrains.kotlin.name.NativeStandardInteropNames +import org.jetbrains.kotlin.name.NativeStandardInteropNames.cInteropPackage +import org.jetbrains.kotlin.objcexport.analysisApiUtils.getDeclaredSuperInterfaceSymbols +import org.jetbrains.kotlin.objcexport.analysisApiUtils.getSuperClassSymbolNotAny +import org.jetbrains.kotlin.objcexport.analysisApiUtils.objCErrorType +import org.jetbrains.kotlin.objcexport.extras.withRequiresForwardDeclaration + +/** + * ClassId for 'kotlinx.cinterop.ObjCClass' + */ +private val objCClassClassId = ClassId(cInteropPackage, Name.identifier("ObjCClass")) + +/** + * ClassId for 'kotlinx.cinterop.ObjCProtocol' + */ +private val objCProtocolClassId = ClassId(cInteropPackage, Name.identifier("ObjCProtocol")) + + +/** + * Special type translation for types that implement `ObjCObject`: + * Such types cannot be directly translated but need to use a supertype that is explicitly marked with an + * `@ExternalObjCClass` annotation. (See [org.jetbrains.kotlin.objcexport.analysisApiUtils.isObjCObjectType] + */ +context(KtAnalysisSession, KtObjCExportSession) +internal fun KtType.translateToObjCObjectType(): ObjCNonNullReferenceType { + if (this !is KtNonErrorClassType) return objCErrorType + val classSymbol = this.classSymbol as? KtClassOrObjectSymbol ?: return ObjCIdType + return classSymbol.translateToObjCObjectType() +} + +context(KtAnalysisSession, KtObjCExportSession) +private fun KtClassOrObjectSymbol.translateToObjCObjectType(): ObjCNonNullReferenceType { + if (isObjCMetaClass()) return ObjCMetaClassType + if (isObjCProtocolClass()) return ObjCClassType("Protocol").withRequiresForwardDeclaration() + + if (isExternalObjCClass() || isObjCForwardDeclaration()) { + return if (classKind == KtClassKind.INTERFACE) { + ObjCProtocolType(nameOrAnonymous.asString().removeSuffix("Protocol")).withRequiresForwardDeclaration() + } else { + ObjCClassType(nameOrAnonymous.asString()).withRequiresForwardDeclaration() + } + } + + return getSuperClassSymbolNotAny()?.translateToObjCObjectType() ?: ObjCIdType +} + +context(KtAnalysisSession) +private fun KtClassOrObjectSymbol.isObjCMetaClass(): Boolean { + if (classIdIfNonLocal == objCClassClassId) return true + return getDeclaredSuperInterfaceSymbols().any { superInterfaceSymbol -> superInterfaceSymbol.isObjCMetaClass() } +} + +context(KtAnalysisSession) +private fun KtClassOrObjectSymbol.isObjCProtocolClass(): Boolean { + if (classIdIfNonLocal == objCProtocolClassId) return true + return getDeclaredSuperInterfaceSymbols().any { superInterfaceSymbol -> superInterfaceSymbol.isObjCProtocolClass() } +} + +context(KtAnalysisSession) +private fun KtClassOrObjectSymbol.isExternalObjCClass(): Boolean { + return hasAnnotation(NativeStandardInteropNames.externalObjCClassClassId) +} + +context(KtAnalysisSession) +private fun KtClassOrObjectSymbol.isObjCForwardDeclaration(): Boolean { + val classId = classIdIfNonLocal ?: return false + return when (NativeForwardDeclarationKind.packageFqNameToKind[classId.packageFqName]) { + null, NativeForwardDeclarationKind.Struct -> false + NativeForwardDeclarationKind.ObjCProtocol, NativeForwardDeclarationKind.ObjCClass -> true + } +} diff --git a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCType.kt b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCType.kt index 5f37706669e..3022e892f1f 100644 --- a/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCType.kt +++ b/native/objcexport-header-generator/impl/analysis-api/src/org/jetbrains/kotlin/objcexport/translateToObjCType.kt @@ -89,7 +89,7 @@ internal fun KtType.mapToReferenceTypeIgnoringNullability(): ObjCNonNullReferenc if (isObjCObjectType()) { // KT-65891: mapObjCObjectReferenceTypeIgnoringNullability - return ObjCIdType + return translateToObjCObjectType() } /* Check if inline type represents 'regular' inline class */ diff --git a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportTypeTranslationTest.kt b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportTypeTranslationTest.kt index 19b8f27e17b..2003ac45d23 100644 --- a/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportTypeTranslationTest.kt +++ b/native/objcexport-header-generator/test/org/jetbrains/kotlin/backend/konan/tests/ObjCExportTypeTranslationTest.kt @@ -567,6 +567,57 @@ class ObjCExportTypeTranslationTest( assertEquals("NSArray *", header.renderTypesOfSymbol("foo")) } + @Test + fun `test - ObjCObject - no ExternalObjCClass annotation`() { + val header = header( + """ + class Foo : kotlinx.cinterop.ObjCObject + val foo: Foo get() = error("stub") + """.trimIndent() + ) + + assertEquals("id", header.renderTypesOfSymbol("foo")) + } + + @Test + fun `test - ObjCObject - with ExternalObjCClass annotation`() { + val header = header( + """ + @kotlinx.cinterop.ExternalObjCClass + class Foo: kotlinx.cinterop.ObjCObject + val foo: Foo get() = error("stub") + """.trimIndent() + ) + + assertEquals("Foo *", header.renderTypesOfSymbol("foo")) + } + + @Test + fun `test - ObjCObject - with ExternalObjCClass annotation - in super class`() { + val header = header( + """ + @kotlinx.cinterop.ExternalObjCClass + open class A: kotlinx.cinterop.ObjCObject + class Foo: A() + val foo: Foo get() = error("stub") + """.trimIndent() + ) + + assertEquals("A *", header.renderTypesOfSymbol("foo")) + } + + @Test + fun `test - ObjCObject - with ObjCClass supertype`() { + val header = header( + """ + class Foo: kotlinx.cinterop.ObjCClass + val foo: Foo get() = error("stub") + """.trimIndent() + ) + + assertEquals("Class", header.renderTypesOfSymbol("foo")) + } + private fun header( @Language("kotlin") vararg sourceCode: String, configuration: HeaderGenerator.Configuration = HeaderGenerator.Configuration(),